+++ /dev/null
-Qt Frame Agreement
-Version 2024-02
-
-1. PARTIES OF THIS AGREEMENT
-1.1. This Qt Frame Agreement—comprised of these general terms together with the appendices attached hereto, (hereinafter “Agreement”) is made by and between: The Qt Company, as defined below (hereinafter ”The Qt Company”) AND Customer name (hereinafter “Customer"):___________________ Business Id (e.g. VAT or EIN number):___________________
-1.2. The parties above are hereinafter individually referred to as a "Party" and collectively as the "Parties".
-
-2. STRUCTURE AND OBJECT OF THE AGREEMENT
-2.1. The Parties have entered into this Agreement to agree on the terms and conditions applicable to The Qt Company's delivery of products and services ("Services") to Customer.
-2.2. This Agreement is comprised of the following components:
-(i) This Agreement, which contains the general terms applicable to all Services,
-(ii) Appendices for each of the Services, containing terms applicable to that individual set of Services ("Service Terms"),
-(iii) a Qt Appendix for Pricing, if applicable, which contains pricing for specific Services, and
-(iv) other topic-specific appendices, such as Support or Marketing Rights.
-2.3. Any and all Services purchased shall be specified in, and agreed upon between, the Parties under a separate purchase order, statement of work, quote, or similar document ("Purchase Document"). Each Purchase Document concluded under this Agreement shall include a reference to this Agreement and be governed by this Agreement.
-
-3. DEFINITIONS
-3.1. "Affiliate" of a Party shall mean an entity (i) which is directly or indirectly controlling such Party; (ii) which is under the same direct or indirect ownership or control as such Party; or (iii) which is directly or indirectly owned or controlled by such Party. For these purposes, an entity shall be treated as being controlled by another if that other entity has fifty percent (50 %) or more of the votes in such entity, is able to direct its affairs and/or to control the composition of its board of directors or equivalent body.
-3.2. "Contractor" shall mean third-party consultants, distributors and contractors performing services to Customer under an applicable contractual arrangement.
-3.3. "Customer" shall mean the individual or legal entity specified in Section 1 above, that is a Party to this Agreement.
-3.4. "Force Majeure Event" shall have the meaning set forth in Section 11.7.
-3.5. "Licensed Software" shall mean The Qt Company's commercial software product which is licensed for use by Customer under this Agreement and corresponding Service Terms. Licensed Software shall include, if and to the extent applicable and specified in the applicable relevant Service Terms, corresponding online or electronic documentation, associated media and printed materials, including the source code, and example programs. The Qt Company may in the course of its development activities, at its free and absolute discretion and without any obligation to send or publish any notifications to Customer or in general, make changes, additions or deletions in the components and functionalities of the Licensed Software, provided that no such changes, additions or deletions will affect the already released version of the Licensed Software, but only upcoming version(s). Licensed Software is commercial computer software, developed at private expense and offered to the public under standard commercial terms.
-3.6. "Professional Services" shall mean The Qt Company's professional-, consulting-, training- and/or project services delivered to Customer under this Agreement and specified in a Purchase Document.
-3.7. "Support" shall mean maintenance and support services provided by The Qt Company to assist Customer in using the Licensed Software, as further specified in the Appendix for Support Terms.
-3.8. "The Qt Company" shall mean:
-(i) in the event Customer is an entity residing in the United States or a legal entity incorporated in or having its headquarters in the United States, The Qt Company Inc., a Delaware corporation with its office at 3031 Tisch Way, 110 Plaza West, San Jose, CA 95128, USA.; or
-(ii) in the event Customer is an entity residing outside of the United States or a legal entity incorporated or having its registered office outside of the United States, The Qt Company Oy., a Finnish company with its registered office at Miestentie 7, 02150 Espoo, Finland.
-
-4. PRICES AND PAYMENT
-4.1. The Qt Company agrees to make Services available to Customer subject to the prices set forth in the Appendix for Pricing. In the event that the Appendix for Pricing does not include a price for certain Services, the applicable price shall be the price agreed by the Parties in the respective Purchase Document.
-4.2. All prices are exclusive of value added tax or other taxes, levels, or duties. Value added tax as well as other possible public charges imposed by authorities shall be added to the prices.
-4.3. All fees under this Agreement are non-cancellable and non-refundable.
-4.4. All fees under this Agreement shall be paid by Customer no later than thirty (30) days from the date of the applicable invoice from The Qt Company.
-4.5. Unless otherwise agreed or provided in the respective Service Terms or Purchase Document, The Qt Company will invoice fees for:
-4.5.1. Licensed Software and Support in advance upon conclusion of the Purchase Document, and
-4.5.2. Professional Services monthly in arrears after the Service has been performed.
-4.6. A late payment charge of the lower of: (a) one percent (1%) per month; or (b) the highest interest rate stipulated by applicable law, shall be charged on any unpaid balances that remain past due and which have not been disputed by Customer in good faith within thirty (30) days of receipt of invoice from The Qt Company.
-4.7. The Qt Company may either (i) invoice Customer based on existing agreement, (ii) request Customer to place a purchase order corresponding to a quote by The Qt Company, or (iii) use Customer's stored Credit Card information to automatically charge the Customer for the relevant Renewal Term.
-4.8. Unless and to the extent otherwise agreed in the Appendix for Pricing or in the Purchase Document, The Qt Company shall be entitled to adjust the prices set forth in the Appendix for Pricing by notifying Customer of the change in writing at least sixty (60) days before the effective date of the change. The change shall not affect the current pricing term of Services agreed upon before the effective date of the change.
-
-5. CONFIDENTIALITY
-5.1. The Parties shall keep confidential, and shall not use or disclose to any unauthorized third parties, the existence and content of this Agreement as well as any Confidential Information received from the other Party or otherwise learned in connection with the Agreement or the performance of the Services, without the prior written consent of the other Party. Confidential Information shall mean information that is designated as confidential or that would be reasonably understood to be confidential given the circumstances of disclosure and the nature of the information. The Parties shall not use Confidential Information received from the other Party for any other purposes than the performance of the Agreement or the fulfilment of their rights and obligations hereunder.
-5.2. Each Party shall limit access the other Party's Confidential Information only to those of its employees, subcontractors, Contractors, Affiliates or financial or legal advisors who necessarily need access to the Confidential Information for the proper performance of the Party's rights and obligations under the Agreement. Each Party shall ensure that the persons receiving Confidential Information of the other Party are bound by confidentiality obligations not less restrictive than those stipulated herein.
-5.3. Each Party shall protect the confidentiality of the other Party's Confidential Information with at least the same degree of security as it exercises to its own confidential information, but no less than a standard of reasonable care.
-5.4. The confidentiality obligation stipulated herein shall not be applied to material and information which:
-(iii) has become generally available or otherwise public prior to its submission by the other Party;
-(iv) becomes generally available or otherwise public due to a reason other than the negligence or omission of the recipient or its personnel or other actions in violation of this Agreement or applicable legislation;
-(v) the Party has lawfully received from a third party without any obligation of confidentiality;
-(vi) was lawfully in the possession of the receiving Party prior to receipt of the same from the other Party without any obligation of confidentiality related thereto;
-(vii) a Party has developed independently without using material or information received from the other Party; or
-(viii) a Party must disclose pursuant to law, decree or other order issued by competent regulatory or governmental body or other public authority or a judicial order, in which case the Party shall, to the extent permitted by applicable law, inform the other Party in writing of the disclosure of information prior to such disclosure.
-5.5. Each Party shall, upon request of the other Party at any time, including upon termination, cancellation or expiry of the Agreement, promptly destroy or deliver to the other Party any and all the documents, files, copies and material containing Confidential Information of the other Party. Notwithstanding the foregoing, a Party may retain one copy of the Confidential Information in a secure location, if and solely to the extent required to comply with applicable laws or regulations. Any Confidential Information stored in electronic back-up form shall be rendered inaccessible and destroyed in accordance with standard back-up procedures.
-
-6. INTELLECTUAL PROPERTY RIGHTS
-6.1. Unless and to the extent expressly provided in the respective Service Terms, this Agreement carries no assignment or license to the intellectual property rights of either Party and all such rights are and shall remain the exclusive property of the Party to whom such rights are vested under applicable law at the signing of this Agreement or thereafter.
-6.2. Where The Qt Company's delivery includes any materials owned by a third party, such third party materials shall be governed in all respects by the applicable license terms of such third-party right holders. The Qt Company shall duly inform the Customer whenever such third party materials are included in the Services and of applicable license terms to be followed by the Customer in using such third party materials.
-
-7. FEES AND ORDERING
-7.1. Services Fees. Services Fees are described in the Purchase Document.
-7.2. Ordering Services.
-(i) Customer may purchase Services pursuant to agreed pricing terms or, if no specific pricing terms have been agreed upon, at The Qt Company's standard pricing terms applicable at the time of purchase.
-(ii) Unless expressly otherwise agreed, any price or other term quoted to Customer shall only be valid for the thirty (30) days from the date such price has been quoted.
-
-8. LIMITED WARRANTY AND WARRANTY DISCLAIMER
-8.1. The Qt Company hereby represents and warrants that: (i) it has the power and authority to grant the rights and licenses granted to Customer under this Agreement; (ii) the Licensed Software will operate materially in accordance with its specifications (as set forth in the applicable product documentation or, where relevant, program description); (iii) Professional Services and Support will be performed in a professional, workmanlike manner pursuant to the Agreement; and (iv) during the ten years prior to the effective date of this Agreement, there have not been any claims alleging that the Licensed Software has infringed any intellectual property rights of a third party and, to the knowledge of The Qt Company as of the effective date of this Agreement, no such infringement exists. These warranties do not apply to issues arising from, or relating to, any third-party materials or Customer's use of the Licensed Software in violation of applicable law or the terms of this Agreement.
-8.2. Except to the extent set forth above, the Services are delivered to Customer "as is" and to the maximum extent permitted by applicable law, exclusive of other warranties, whether express, implied, or otherwise. Customer's sole and exclusive remedy and The Qt Company's entire liability for deficiencies or errors in the Services shall be limited, at The Qt Company's option, to correction of the error, replacement of the Services, re-performance of the Service or return of the applicable fees paid for the defective Service for the time period during which Customer was not able to utilize the Service as agreed.
-
-9. LIMITATION OF LIABILITY
-9.1. EXCEPT FOR (I) CASES OF GROSS NEGLIGENCE OR INTENTIONAL MISCONDUCT, (II) A BREACH OR VIOLATION OF THE OTHER PARTY'S INTELLECTUAL PROPERTY RIGHTS, OR (III) WHERE REQUIRED BY APPLICABLE LAW, IN NO EVENT SHALL EITHER PARTY BE LIABLE TO THE OTHER PARTY FOR ANY LOST PROFITS, LOSS OF DATA, LOSS OF BUSINESS OR GOODWILL OR ANY OTHER INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE COST, DAMAGES OR EXPENSE OF ANY KIND, HOWSOEVER ARISING UNDER OR IN CONNECTION WITH THIS AGREEMENT.
-9.2. EXCEPT FOR (I) CASES OF GROSS NEGLIGENCE OR INTENTIONAL MISCONDUCT, (II) A BREACH OR VIOLATION OF THE OTHER PARTY'S INTELLECTUAL PROPERTY RIGHTS, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL EITHER PARTY'S TOTAL AGGREGATE LIABILITY UNDER THIS AGREEMENT EXCEED THE AGGREGATE FEES PAID OR PAYABLE TO THE QT COMPANY BY CUSTOMER FOR THE RESPECTIVE LICENSED SOFTWARE OR SERVICE GIVING RISE TO THE LIABILITY. THE FOREGOING LIMITATION WILL NOT APPLY TO CUSTOMER'S OBLIGATION TO PAY THE APPLICABLE FEES CORRESPONDING TO ITS ACTUAL USE OF LICENSED SOFTWARE OR SERVICES.
-
-10. TERM AND TERMINATION
-10.1. This Agreement shall enter into force upon signing by both Parties and is effective as of the last date of signature.
-10.2. This Agreement shall remain in force until further notice and may be terminated without cause by either Party by no less than three (3) months' prior written notice to the other Party.
-10.3. Termination of a particular Purchase Document and the Services governed thereunder shall be stipulated under the applicable Service Terms.
-10.4. Either Party may terminate this Agreement with immediate effect, if the other Party:
-(i) commits a material breach of the terms of this Agreement (including applicable Service Terms) and has not remedied such breach within a reasonable period of time (which shall be no less than thirty (30) days) of the non-breaching Party's written notice specifying the breach, or
-(ii) becomes bankrupt, insolvent or goes into liquidation or debt restructuring.
-10.5. Termination of this Agreement shall, as such, have no effect on the validity of any Services ordered and agreed prior to the effective date of such termination, and such Services shall continue to remain in force pursuant to applicable Service Terms (including the terms of this Agreement) for the remainder of the duration of the applicable Service validity term.
-
-11. GOVERNING LAW AND DISPUTE RESOLUTION
-11.1. The United Nations Convention on Contracts for the International Sale of Goods will not apply to this Agreement.
-11.2. Where this Agreement is concluded with The Qt Company, Inc., a Delaware corporation, the Parties agree that this Section 10.2 will apply. This Agreement will be governed by, and construed in accordance with the laws of the State of California and any controlling United States federal law. Any dispute, controversy or claim arising out of or relating to this contract, including the formation, interpretation, breach or termination thereof, and whether the claims asserted are arbitrable, will be referred to and finally determined by arbitration in accordance with the JAMS International Arbitration Rules. The tribunal will consist of one arbitrator. The place of arbitration will be San Francisco, California, USA. The language to be used in the arbitral proceedings will be English. Judgment upon the award rendered by the arbitrator(s) may be entered in any court having jurisdiction thereof. This Section 10.2 shall not preclude parties from seeking provisional remedies in aid of arbitration from a court of appropriate jurisdiction. Notwithstanding the foregoing, any action by The Qt Company solely to collect license or other fees hereunder may be brought in any court of competent jurisdiction.
-11.3. Where this Agreement is concluded with The Qt Company, Oy., a Finnish company, the parties agree that this Section 10.3 will apply. This Agreement shall be construed and interpreted in accordance with the laws of Finland, excluding its choice of law provisions. All disputes arising out of or in connection with this Agreement shall be finally settled in accordance with the laws of Finland, excluding its choice of law provisions. All disputes arising out of or in connection with this Agreement shall be finally settled under the Rules of Arbitration of the International Chamber of Commerce by one or more arbitrators appointed in accordance with the said Rules. The place of arbitration will be Helsinki, Finland. The language to be used in arbitral proceedings will be English. This Section 10.3 shall not preclude parties from seeking provisional remedies in aid of arbitration from a court of appropriate jurisdiction.
-
-12. MISCELLANOUS
-12.1. No Assignment. Customer shall not be entitled to assign or transfer all or any of its rights, benefits and obligations under this Agreement except in case of sale of relevant business or assets or otherwise with prior written consent of The Qt Company, which shall not be unreasonably withheld or delayed. The Qt Company shall be entitled to freely assign or transfer any of its rights, benefits or obligations under this Agreement.
-12.2. Surviving Sections. Any terms and conditions that by their nature or otherwise reasonably should survive termination of this Agreement shall so be deemed to survive.
-12.3. Entire Agreement. This Agreement, its Appendices and any applicable Purchase Documents constitute the complete agreement between the Parties and supersedes all prior or contemporaneous discussions, representations, contracts (including prior License Agreements and similar prior agreements), and proposals, written or oral, with respect to the subject matters discussed herein.
-12.4. Subcontractors. The Qt Company may utilize subcontractors in the performance of Services under this Agreement, provided that The Qt Company remains responsible for the performance of the Services and compliance with this Agreement, as well as ensuring that subcontractors are required to abide by relevant restrictions (e.g., confidentiality) set forth in this Agreement.
-12.5. Modifications. No modification of this Agreement shall be effective unless contained in a writing executed by an authorized representative of each Party. No standard terms and conditions or provisions of any Customer purchase order or other ordering form that Customer may use in connection with the acquisition of Services will modify or affect this Agreement, the parties agree that any such terms and conditions are void with no legal effect.
-12.6. Affiliate Orders. Customer Affiliates may purchase Services via this Agreement as follows:
-(i) any purchases by Customer Affiliates from The Qt Company or its Affiliates will create a contractual relationship directly between the relevant The Qt Company entity and the respective ordering Customer Affiliate;
-(ii) the entry into a Purchase Document between The Qt Company and Customer Affiliate creates an agreement between The Qt Company and Customer Affiliate and incorporates all terms and conditions of this Agreement as the governing agreement between The Qt Company and Customer Affiliate ("Accession Agreement"): and
-(iii) Customer Affiliate will be deemed "Customer" under the terms of this Agreement and all rights and obligations under such Accession Agreement are vested and borne solely by the ordering Customer Affiliate and the relevant The Qt Company entity as contracting parties under such Accession Agreement.
-12.7. Force Majeure. Neither Party shall be liable to the other for any delay or non-performance of its obligations hereunder in the event and to the extent that such delay or non-performance is due to an event of act of God, terrorist attack or other similar unforeseeable catastrophic event that prevents either Party for fulfilling its obligations under this Agreement and which such Party cannot avoid or circumvent ("Force Majeure Event"). If the Force Majeure Event results in a delay or non-performance of a Party for a period of three (3) months or longer, then either Party shall have the right to terminate the relevant Purchase Document and Services thereunder with immediate effect without any liability (except for the obligations of payment arising prior to the Force Majeure Event) towards the other Party.
-12.8. Notices. Any notice given by one Party to the other shall be deemed properly given and deemed received if specifically acknowledged by the receiving Party in writing or when successfully delivered to the recipient by hand, fax, or special courier during normal business hours on a business day to the addresses specified for each Party in this Agreement. Each communication and document made or delivered by one Party to the other Party pursuant to this Agreement shall be in the English language.
-12.9. Attorney Fees. The prevailing Party in any action to enforce this Agreement shall be entitled to recover its attorney's fees and costs in connection with such action.
-12.10. Privacy and Security. The Parties commit to and comply with their respective applicable obligations under the privacy and security terms set forth in the Privacy and Security Appendix and relevant Appendices attached hereto.
-12.11. Feedback. Customer agrees that, from time to time, The Qt Company, may request feedback from Customer regarding the Services ("Feedback"). Customer may choose to provide Feedback and agrees that The Qt Company may freely use, copy, disclose, and exploit any Feedback. No Feedback will be considered Customer Confidential Information unless explicitly agreed otherwise between the Parties.
-12.12. Export Control. Customer acknowledges that the Services, or portions thereof, may be subject to export control restrictions under the applicable laws of respective countries. Customer shall fully comply with all applicable export license restrictions and requirements, economic sanctions restrictions, as well as with all laws and regulations relating thereto, and shall procure all necessary governmental authorizations, including without limitation, all necessary licenses, approvals, permissions, or consents, where necessary (e.g., for re-exportation of the Redistributables, Applications and/or Devices, each as defined in the relevant Service Terms).
-12.13. Severability. If any provision of this Agreement shall be adjudged by any court of competent jurisdiction to be unenforceable or invalid, that provision shall be limited or eliminated to the minimum extent necessary so that this Agreement shall otherwise remain in full force and effect and enforceable.
-
-13. APPENDICES
-13.1. The following appendices form an integral part of this Agreement. In case of a discrepancy between this Agreement and any of its Appendices, this Agreement shall prevail. In case of discrepancies between the Purchase Document(s) and this Agreement or applicable Service Terms, the terms of this Agreement or the applicable Service Terms shall prevail, except in cases where an express deliberate deviation from the terms of this Agreement or applicable Service Terms has been concluded pursuant to Section 2.3 hereof, in which case the Purchase Document shall prevail.
-1. Appendix for Qt Development Framework
-2. Appendix for Support Terms https://www.qt.io/terms-conditions/support-terms
-3. Appendix for Privacy and Security Terms https://www.qt.io/terms-conditions/privacy-and-security
-
-Appendix for Qt Development Framework
-Version 2024-02
-
-1. This Appendix for Qt Development Framework is an integral part of the Agreement and specifies the legal terms for the licensing of Licensed Software (as defined below) between The Qt Company and the Customer. Entry into this Appendix governs the use of and supersedes any prior contracts between the Parties (including prior License Agreements and similar prior agreements), with respect to the Licensed Software under this Appendix.
-
-2. DEFINITIONS
-2.1. Capitalized words used in this Appendix shall have the meanings described in the Agreement or as defined below.
-2.2. "Add-on Products" shall mean The Qt Company's specific add-on software products which are not licensed as part of The Qt Company's standard Services offerings, but shall be included into the scope of Licensed Software only if so specifically agreed between the Parties.
-2.3. "Application" means software products created using the Licensed Software, which include the Redistributables, or part thereof.
-2.4. "End Customer" shall mean Customer's customer(s) to whom Customer, directly or indirectly, distributes copies of the Redistributables as integrated or incorporated into Applications or Devices.
-2.5. "Data Protection Legislation" shall mean the General Data Protection Regulation (EU 2016/679) (GDPR) and any national implementing laws, regulations and secondary legislation, as may be amended or updated from time to time, as well as any other data protection laws or regulations applicable in the relevant territory.
-2.6. "Deployment Platforms" shall mean target operating systems and/or hardware specified in the License Certificate, on which the Redistributables can be distributed pursuant to the terms and conditions of this Appendix.
-2.7. "Designated User(s)" shall mean the employee(s) of Customer or Customer's Affiliates acting within the scope of their employment or Customer's Contractors acting within the scope of their services on behalf of Customer.
-2.8. "Development License" shall mean the license needed by the Customer for each Designated User to use Licensed Software under the license grant described in Section 5 of this Appendix. Development Licenses are available per respective Licensed Software products; each product having its designated scope and purpose of use.
-2.9. "Development Platforms" shall mean the host operating system(s) specified in the License Certificate, on which Licensed Software can be used under the Development License.
-2.10. "Devices" shall mean
-(i) hardware devices or products that
-a. are manufactured and/or distributed by the Customer, its Affiliates, Contractors or End Customer, and
-b. incorporate, integrate or link to Applications such that substantial functionality of such unit, when used by an End User, is provided by Application(s) or otherwise depends on the Licensed Software; or
-(ii) Applications designed for the hardware devices specified in item (i).
-Devices covered by this Appendix shall be specified in the Pricing Appendix or Purchase Document.
-2.11. "Distribution License(s)" shall mean a royalty-bearing license required for any kind of sale, trade, exchange, loan, lease, rental or other distribution by or on behalf of Customer to a third party of Redistributables in connection with Devices pursuant to license grant described in Section 5.3 of this Appendix. Distribution Licenses are sold separately for each type of Device respectively and cannot be used for any other type of Devices.
-2.12. "Distribution License Packs" shall mean set of prepaid Distribution Licenses for distribution of Redistributables, as defined in The Qt Company's standard price list, quote, Pricing Appendix or in the Purchase Document, as applicable.
-2.13. "Evaluation License Term" shall mean a time period specified in the License Certificate for the Customer to use the relevant Licensed Software for evaluation purposes according to Section 5.6 of this Appendix.
-2.14. "Intellectual Property Rights" shall mean patents (including utility models), design patents, and designs (whether or not capable of registration), chip topography rights and other like protection, copyrights, trademarks, service marks, trade names, logos or other words or symbols and any other form of statutory protection of any kind and applications for any of the foregoing as well as any trade secrets.
-2.15. "License Certificate" shall mean a certificate generated by The Qt Company for each Designated User respectively upon their download of the Licensed Software, which will be available under the respective Designated User's Qt Account at account.qt.io. License Certificates will specify relevant information pertaining to the Licensed Software purchased by Customer and the license to the Licensed Software.
-2.16. "License Fee" shall mean the fee charged to Customer for rights granted under this Appendix.
-2.17. "Licensed Software" shall mean the specified product(s) of Qt Software which Customer has purchased and which is provided to Customer under the terms of this Appendix (including its Exhibits). Licensed Software shall include corresponding online or electronic documentation, associated media and printed materials, including source code (where applicable), example programs and the documentation. Licensed Software does not include Third Party Software (as defined in Section 6) or Qt Community Edition. The Qt Company may, in the course of its development activities, at its free and absolute discretion and without any obligation to send or publish any notifications to Customer or in general, make changes, additions or deletions in the components and functionalities of the Licensed Software, provided that no such changes, additions or deletions will affect the already released version of the Licensed Software, but only upcoming version(s).
-2.18. "License Term" shall mean the agreed validity period of the Development License during which the relevant Licensed Software product can be used pursuant to this Appendix. The agreed License Term, as ordered and paid for by Customer, shall be memorialized in the applicable License Certificate.
-2.19. "Customer's Records" shall mean books and records that contain information bearing on Customer's compliance with the Agreement, Customer's use of Qt Community Edition and/or the payments due to The Qt Company under the Agreement, including, but not limited to user information, assembly logs, sales records and distribution records.
-2.20. "Modified Software" shall have the meaning as set forth below in Section 4.
-2.21. "Qt Software" shall mean the development and design software of The Qt Company, which The Qt Company makes available under commercial and/or open source licenses as either the "Licensed Software" or the "Qt Community Edition".
-2.22. "Permitted Software" shall mean third party products that are generally available to the public, which may include parts of Qt Community Edition or be developed using Qt Community Edition.
-2.23. "Pre-Release Code" shall have the meaning as set forth in Section 7.
-2.24. "Prohibited Combination" shall mean any effort to use, combine, incorporate, link or integrate Licensed Software with any software created with or incorporating Qt Community Edition, or use Licensed Software for creation of any such software.
-2.25. "Qt Community Edition" shall mean the open source version of Qt Software available under the terms of the GNU Lesser General Public License, version 2.1 or later ("LGPL") or the GNU General Public License, version 2.0 or later ("GPL"). For clarity, Qt Community Edition shall not be provided, governed or used under this Appendix.
-2.26. "Redistributables" shall mean the portions of Licensed Software as set forth in Exhibit 1 hereto that may be distributed pursuant to this Appendix in object code form only, including any relevant documentation. Where relevant, any reference to Licensed Software in this Appendix includes and refers to Redistributables.
-2.27. "Renewal Term" shall mean an extension of the previous License Term as agreed between the Parties.
-2.28. "Submitted Modified Software" shall have the meaning as set forth in Section 4.2 of this Appendix.
-2.29. "Third-Party Software" shall have the meaning set forth in Section 6 of this Appendix.
-2.30. "Updates" shall mean a release or version of the Licensed Software containing bug fixes, error corrections and other changes that are generally made available to users of the Licensed Software that have contracted for Support. Updates are generally depicted as a change to the digits following the decimal in the Licensed Software version number. The Qt Company shall make Updates available to Customer under the Support. Updates shall be considered as part of the Licensed Software hereunder.
-2.31. "Upgrades" shall mean a release or version of the Licensed Software containing enhancements and new features and are generally depicted as a change to the first digit of the Licensed Software version number. In the event that Upgrades are provided to Customer under this Appendix, they shall be considered as part of the Licensed Software hereunder.
-
-3. OWNERSHIP
-3.1. Ownership of The Qt Company
-3.1.1. The Licensed Software is protected by copyright laws and international copyright treaties, as well as other intellectual property laws and treaties. The Licensed Software is licensed, not sold.
-3.1.2. All of The Qt Company's Intellectual Property Rights are and shall remain the exclusive property of The Qt Company or its respective licensors . No rights to The Qt Company's Intellectual Property Rights are assigned or granted to Customer under this Appendix, except when and to the extent expressly specified herein.
-3.2. Ownership of Customer
-3.2.1. All of Customer's Intellectual Property Rights are and shall remain the exclusive property of Customer or its licensors respectively.
-3.2.2. Except to the extent set forth in this Appendix, all Intellectual Property Rights to the Modified Software, Applications and Devices (except to Redistributables included therein) shall remain with Customer.
-
-4. MODIFIED SOFTWARE
-4.1. Customer may create bug-fixes, error corrections, patches or modifications to the Licensed Software ("Modified Software"). To the extent that Customer's Modified Software breaks source or binary compatibility or other functionality with the Licensed Software, Customer acknowledges that The Qt Company's ability to provide Support may be prevented or limited and Customer's ability to make use of Updates may be restricted.
-4.2. Customer may, at its sole and absolute discretion, choose to submit Modified Software to The Qt Company ("Submitted Modified Software") in connection with Customer's Support request, service request or otherwise. In the event Customer does so, then, Customer hereby grants The Qt Company a sublicensable, assignable, irrevocable, perpetual, worldwide, non-exclusive, royalty-free and fully paid-up license, under all of Customer's Intellectual Property Rights, to reproduce, adapt, translate, modify, and prepare derivative works of, publicly display, publicly perform, sublicense, make available and distribute such Submitted Modified Software as The Qt Company sees fit at its free and absolute discretion.
-
-5. LICENSES GRANTED
-5.1. Development with Licensed Software
-5.1.1. Subject to the terms of the Agreement, The Qt Company grants to Customer a worldwide, non-exclusive, non-transferable license, valid for each License Term, to use, modify and copy the Licensed Software by Designated Users on the Development Platforms for the sole purposes of designing, developing, demonstrating and testing Application(s) and/or Devices, and to provide support and other services related to such Applications and Devices to End Customers. Each Application and/or Device can only include, incorporate or integrate contributions by such Designated Users who are duly licensed for the applicable Development Platform(s) and Deployment Platform(s) (i.e have a valid license for the appropriate Licensed Software product and only use one type of Qt Development License per Customer Application and/or Device(s)).
-5.1.2. Customer may install copies of the Licensed Software on five (5) computers per Designated User, provided that only Designated Users who have a valid Development License may use the Licensed Software.
-5.1.3. Customer may designate another Designated User to replace a then-current Designated User by notifying The Qt Company in writing, where such replacement is due to termination of employment, long-term absence or other permanent reason affecting Designated User's need for Licensed Software.
-5.1.4. Upon expiry of the initially agreed License Term, the respective License Term shall be automatically extended by one or more Renewal Term(s), unless and until either Party notifies the other Party in writing, that it does not wish to continue the License Term, such notification to be provided to the other Party no less than thirty (30) days before expiry of the respective License Term. The Qt Company shall, in good time before the due date for the above notification, remind the Customer on the coming Renewal Term. Unless otherwise agreed between the Parties, Renewal Term shall be equal to the length of the previous License Term, but no longer than thirty-six (36) months.
-5.1.5. Any such Renewal Term shall be subject to License Fees agreed between the Parties or, if no advance agreement exists, subject to The Qt Company's standard list pricing applicable at the commencement date of any such Renewal Term.
-5.2. Distribution of Applications
-5.2.1. Subject to the terms of the Agreement, The Qt Company grants to Customer a worldwide, non-exclusive, non-transferable, perpetual, royalty-free and revocable (only for Customer’s material breach of agreement) right and license to:
-(i) distribute, by itself or through its Contractors, Redistributables as installed, incorporated or integrated into Applications for execution on the Deployment Platforms; and
-(ii) grant perpetual and irrevocable sublicenses to Redistributables, as distributed hereunder, for End Customers solely to the extent necessary in order for the End Customers to use the Applications for their respective intended purposes.
-5.2.2. Right to distribute the Redistributables as part of an Application as provided herein is not royalty-bearing but is conditional upon the Application having been created, updated and maintained under a valid and duly paid Development License.
-5.3. Distribution of Devices
-5.3.1. Subject to the terms of the Agreement, The Qt Company grants to Customer a worldwide, non-exclusive, non-transferable, perpetual, revocable (only for Customer’s material breach of agreement), royalty-bearing right and license to:
-(i) distribute, by itself or through one or more tiers of Contractors, Redistributables as installed, incorporated or integrated, or intended to be installed, incorporated or integrated into Devices for execution on the Deployment Platforms; and
-(ii) grant perpetual and irrevocable sublicenses to Redistributables, as distributed hereunder, for End Customers solely to the extent necessary in order for the End Customers to use the Devices for their respective intended purposes.
-5.3.2. Right to distribute the Devices as provided herein is conditional upon (i) the Devices having been created, updated and maintained under a valid and duly paid Development License, and (ii) Customer having acquired corresponding Distribution Licenses at the time of distribution of any Devices to End Customers.
-5.4. Further Requirements
-5.4.1. The licenses granted in this Section 5 by The Qt Company to Customer are conditional and subject to Customer's compliance with the following terms:
-(i) Customer acknowledges that The Qt Company has separate products for the purpose of Applications and Devices respectively, where development and distribution of Devices is only allowed using the correct designated product. Customer shall ensure and bear the burden of proof that Customer is using a correct product entitling Customer to development and distribution of Devices;
-(ii) Customer shall not remove or alter any copyright, trademark or other proprietary rights notice(s) contained in any portion of the Licensed Software;
-(iii) Applications must add primary and substantial functionality to Licensed Software so as not to compete with the Licensed Software;
-(iv) Applications may not pass on functionality which in any way makes it possible for others to create software with Licensed Software; provided however that Customer may use Licensed Software’s scripting and QML ("Qt Quick") functionality solely in order to enable scripting, themes and styles that augment the functionality and appearance of the Application(s) without adding primary and substantial functionality to the Application(s);
-(v) Customer shall not use Licensed Software in any manner or for any purpose that infringes, misappropriates or otherwise violates any Intellectual Property Right or right of any third party, or that violates any applicable law;
-(vi) Customer shall not use The Qt Company's or any of its suppliers' names, logos, or trademarks to market Applications, except that Customer may use “Built with Qt” logo to indicate that an Application or Device was developed using Licensed Software;
-(vii) Customer shall not distribute, sublicense or disclose source code of Licensed Software to any third party (provided however that Customer may appoint employee(s) of Contractors and Affiliates as Designated Users to use Licensed Software pursuant to this Appendix).
-(viii) Customer shall not grant the End Customers a right to: (a) make copies of the Redistributables except when and to the extent required to use the Applications and/or Devices for their intended purpose; (b) modify the Redistributables or create derivative works thereof; (c) decompile, disassemble or otherwise reverse engineer Redistributables; or (d) redistribute any copy or portion of the Redistributables to any third party, except as part of the onward sale of the Application or Device on which the Redistributables are installed;
-(ix) Customer shall not, and shall cause that its Affiliates or Contractors shall not, use Licensed Software in any Prohibited Combination, unless Customer has received specific advance written permission from The Qt Company to do so. Absent such written permission, any and all distribution by Customer during the term of the Agreement of a hardware device or product: a) which incorporates or integrates any part of Licensed Software or Qt Community Edition; or b) where substantial functionality is provided by software built with Licensed Software or Qt Community Edition or otherwise depends on Licensed Software or Qt Community Edition, shall be considered to be Device distribution under this Appendix and shall be dependent on Customer’s compliance thereof (including but not limited to the obligation to pay applicable License Fees for such distribution). Notwithstanding the foregoing, Customer is entitled to use and combine Licensed Software with Permitted Software;
-(x) Customer shall cause all of its Affiliates, Contractors and End Customer entitled to make use of the licenses granted under this Appendix, to be contractually bound to comply with the relevant terms hereof and not to use the Licensed Software beyond the terms hereof nor for any purposes other than operating within the scope of their services for Customer. Customer shall be responsible for any and all actions and omissions of its Affiliates, Contractors, and End Customers relating to the Licensed Software and use thereof (including but not limited to payment of all applicable License Fees);
-(xi) Except when and to the extent explicitly provided in this Section 5, Customer shall not transfer, publish, disclose, display or otherwise make available the Licensed Software; and
-(xii) Customer shall not attempt or enlist a third party to conduct or attempt to conduct any of the above.
-5.4.2. The above terms shall not be applicable if and solely to the extent they conflict with any mandatory provisions of applicable laws.
-5.4.3. Any use of Licensed Software beyond the provisions of this Appendix is strictly prohibited and requires, at a minimum an additional license from The Qt Company (e.g. certain additional rights granted under software development kit “SDK” agreement with regard to limitations of Section 5.4.1 iv, vii or viii).
-5.5. Evaluation License
-5.5.1. Subject to the terms of this Appendix, The Qt Company grants to Customer a worldwide, non-exclusive, non-transferable license, valid for the Evaluation License Term to use the relevant Licensed Software product solely for Customer’s internal use to evaluate and determine whether the Licensed Software meets Customer's business requirements, specifically excluding any commercial use of the Licensed Software or any derived work thereof.
-5.5.2. Upon the expiry of the Evaluation License Term, Customer must either discontinue use of the relevant Licensed Software or acquire a commercial Development License specified herein.
-
-6. THIRD-PARTY SOFTWARE. The Licensed Software may provide links or access to third party libraries or code (collectively "Third-Party Software") to implement various functions. Third-Party Software does not, however, comprise part of the Licensed Software, but is provided to Customer complimentary and use thereof is discretionary for Customer. Third-Party Software will be listed in the ".../src/3rdparty" source tree delivered with the Licensed Software or documented in the Licensed Software, as such may be amended from time to time. Customer acknowledges that use or distribution of Third-Party Software is in all respects subject to applicable license terms of applicable third-party right holders.
-
-7. PRE-RELEASE CODE
-7.1. The Licensed Software may contain pre-release code and functionality, or sample code marked or otherwise stated with appropriate designation such as "Technology Preview", "Alpha", "Beta", "Experimental", "Sample", "Example" etc. ("Pre-Release Code").
-7.2. Such Pre-Release Code may be provided complimentary for Customer, in order to provide experimental support or information for new platforms or preliminary versions of one or more new functionalities, or for other similar reasons. Pre-Release Code may not be at the level of performance and compatibility of a final, generally available, product offering. Pre-Release Code may not operate correctly, may contain errors and may be substantially modified by The Qt Company prior to a commercial product release, if any. The Qt Company is under no obligation to make Pre-Release Code commercially available, or provide any Support or Updates relating thereto. To the maximum extent permitted by law, the Qt Company assumes no liability whatsoever regarding any Pre-Release Code and any use thereof is exclusively at Customer's own risk and expense.
-7.3. Unless Licensed Software specifies different license terms for the respective Pre-Release Code, Customer is entitled to use such pre-release code pursuant to Section 5 of this Appendix, just like other Licensed Software.
-
-8. SUPPORT. Support is provided according to agreed support level and subject to applicable requirements and restrictions, as specified in the Appendix for Support Terms.
-
-9. FEES AND ORDERING: DISTRIBUTION LICENSES
-9.1. Distribution License Packs
-9.1.1. Unless otherwise agreed in writing, Distribution Licenses shall be purchased by way of Distribution License Packs.
-9.1.2. Upon due payment of the ordered Distribution License Pack(s), Customer will have an account of Distribution Licenses available for distributing the Redistributables in accordance with this Agreement.
-9.2. Each time Customer distributes a copy of Redistributables, one Distribution License is used and Customer's account of available Distribution Licenses is decreased accordingly.
-9.3. Customer may distribute copies of the Redistributables so long as Customer has Distribution Licenses remaining on its account.
-
-10. RECORD-KEEPING AND REPORTING OBLIGATIONS; AUDIT RIGHTS
-10.1. Customer's Record-keeping
-10.1.1. Customer shall at all times during the term of the Agreement or validity of any of the licenses hereunder, whichever is later, and for a period of two (2) years thereafter, maintain Customer's Records in an accurate and up-to-date form. Customer's Records shall be adequate to reasonably enable The Qt Company to determine Customer's compliance with the provisions of the Agreement. The records shall conform to general good accounting practices.
-10.1.2. Customer shall, within thirty (30) days from receiving The Qt Company's request to that effect, deliver to The Qt Company a report based on Customer's Records, such report to contain information, in sufficient detail, on: (i) number and identity of users working with Licensed Software or Qt Community Edition, (ii) copies of Redistributables distributed by Customer during the most recent calendar quarter and/or any other term specified by The Qt Company, and (iii) any other information pertaining to Customer's compliance with the terms of the Agreement (e.g. information on products and/or projects relating to use of Distribution Licenses), as The Qt Company may reasonably require from time to time.
-10.2. The Qt Company's Audit Rights
-10.2.1. The Qt Company or an independent auditor acting on behalf of The Qt Company may, upon at least thirty (30) days' prior written notice and at The Qt Company expense, audit Customer with respect to Customer's use of the Licensed Software, but not more frequently than once during each six (6) month period. Such audit may be conducted by mail, electronic means or through an in-person visit to Customer's place of business. Any possible in-person audit shall be conducted during regular business hours at Customer's facilities, shall not unreasonably interfere with Customer's business activities and shall be limited in scope to verify Customer's compliance with the terms of the Agreement. The Qt Company or its independent auditor shall be entitled to inspect Customer's Records and conduct necessary interviews of Customer's relevant employees and Contractors. All Customer's Records and use thereof shall be subject to the obligation of confidentiality under the Agreement.
-10.2.2. If an audit reveals that Customer is using the Licensed Software beyond scope of the licenses Customer has paid for, Customer shall pay to The Qt Company any amounts owed for such unauthorized use within thirty (30) days from receipt of the corresponding invoice from The Qt Company.
-10.2.3. In addition, in the event the audit reveals a material violation of the terms of the Agreement (without limitation, either (i) underpayment of more than 10% of License Fees or 10,000 euros (whichever is more) or (ii) distribution of products, which include or result from Prohibited Combination, shall be deemed a material violation for purposes of this section), then Customer shall pay The Qt Company's reasonable cost of conducting such audit.
-
-11. TERMINATION
-11.1. Termination of Licenses
-11.1.1. The Qt Company may terminate Customer's rights to any and all Licensed Software (including access to Support), if Customer:
-(i) commits a material breach of the Agreement (including this Appendix) and has not remedied the breach within a reasonable period of time (which shall be no less than 30 days) of The Qt Company's written notice specifying the breach, or
-(ii) becomes bankrupt, insolvent or goes into liquidation or debt restructuring.
-11.2. Suspension of rights: Instead of termination, The Qt Company reserves the right to suspend or withhold grants of any and all rights to the Licensed Software (including Support), should Customer fail to make payment in timely fashion or otherwise violate or is reasonably suspected of violating its obligations under the Agreement and/or this Appendix, and where such violation or breach is not cured within ten (10) business days following The Qt Company's written notice thereof.
-11.3. Parties Rights and Duties upon Termination
-11.3.1. Upon expiry or termination of the Development Licenses, Customer shall cease and shall cause all Designated Users (including those of its Affiliates' and Contractors') to cease using the relevant Licensed Software.
-11.3.2. Upon such expiry or termination of Development Licenses, Customer shall destroy or return to The Qt Company all copies of the respective Licensed Software and all related materials and will certify the same by Customer's duly authorized officer to The Qt Company upon its request, provided however that Customer may retain and utilize such copies of the Licensed Software to the extent required to provide Customer's continued support to End Customers, for archiving purposes or as is required under applicable law.
-11.3.3. Distribution Licenses are perpetual and, therefore, Customer's distribution rights hereunder shall only terminate upon The Qt Company's termination of Distribution Licenses due to Customer's material breach as set forth in Section 11.1.1(i) of this Appendix. In case of such termination by The Qt Company due to Customer's material breach, Customer must cease any distribution of Applications and Devices at the effective date of termination.
-11.3.4. Expiry or termination of any of Customer's licenses hereunder for any reason whatsoever shall not:
-(i) relieve Customer of its obligation to pay any License Fees accrued or payable to The Qt Company prior to the effective date of termination, and Customer pay to The Qt Company all such fees within 30 days from the effective date of termination of the licenses;
-(ii) relieve Customer of its obligation to ensure that Applications and Devices (including those already distributed) remain in compliance with the terms of the Agreement; nor
-(iii) affect any rights of End Customer to continue use of Applications and Devices (and therein incorporated Redistributables).
-11.4. Extension of Rights under Special Circumstances. In the event that, during the applicable License Term, The Qt Company is declared bankrupt under a final, non-cancellable decision by relevant court of law, and the Agreement is not, at the date of expiry of the Development License(s), assigned to a party who has assumed The Qt Company's position as a legitimate licensor of Licensed Software under the Agreement, then all valid Development Licenses possessed by Customer at such date of expiry, and which Customer has not notified for expiry, shall be extended to be valid in perpetuity under the terms of the Agreement. Any such extension shall not apply to The Qt Company's Support obligations.
-
-EXHIBIT 1, Licensed Software
-At the time of conclusion of this Appendix, the latest available version of Licensed Software includes the software libraries and tools set forth in Exhibit 1 (as provided below), depending on which product(s) Customer has purchased under the relevant Purchase Document.
-The modules and tools are specific to each product version respectively and may vary from version to version. Modules and tools included in the latest publicly available version of the respective product at any given time are listed in Exhibit 1 of https://www.qt.io/terms-conditions/qt-dev-framework/exhibit-1. If a new version of Licensed Software does not include a module or tool present in an older version which Customer is entitled to use under a valid license from The Qt Company, then Customer will continue to have such right during the validity of Customer's license to relevant Licensed Software. In the event a new version of the Licensed Software adds modules or tools to any previous version(s), Customer's rights will extend to cover also such additional modules and tools.
-
-EXHIBIT 2 - Small Business Terms
-1. This Exhibit applies to entities that qualify as a Qualified Small Business (defined below) and provides additional terms and conditions applicable to small business pricing and licensing. In the event that Customer is a Qualified Small Business and there is any conflict between the terms of this Exhibit and any other terms of the Agreement, the terms in this Exhibit shall take precedence.
-
-2. APPLICABILITY FOR SMALL BUSINESS LICENSES. Any small business discounts applied require that Customer (including any Customer Affiliates or group entities) has an annual revenue (including annual capital funding) below 1 Million EUR, or the equivalent thereof, as approved by The Qt Company (each, a "Qualified Small Business"). The annual revenue, including funding, must be evidenced upon request by business records and approved by The Qt Company in its reasonable discretion.
-
-3. SUPPORT. Support is limited to: (i) Install Support; and (ii) for any other Standard Support issue, five (5) support tickets annually.
-
-4. LIMITATION ON NUMBER OF SMALL BUSINESS DEVELOPER LICENSES. Qualified Small Business discounts and purchasing structure may be applied to a maximum of three discounted developer licenses (either ADE or DCP) per Qualified Small Business. Any additional licenses purchased will be at The Qt Company list price in effect at the time.
-
-5. LIMITATION FOR NUMBER OF INSTALLATIONS. Customer may install copies of the Licensed Software on two (2) computers per Designated User, provided that only the Designated Users who have a valid Development License may use the Licensed Software.
-
-6. CONDITIONAL WAIVER OF DISTRIBUTION LICENSES. For Qualified Small Businesses, the Agreement requirements to purchase Distribution Licenses for Devices shall apply only when Customer ceases to be a Qualified Small Business (e.g., when annual revenue threshholds are bypassed).
-
-7. ADDITIONAL TERMS FOR RENEWALS. The initial subscription purchase term for Qualified Small Business Licenses is twelve (12) months. Upon expiration of the initial twelve (12) month term and unless terminated in accordance with the Agreement, the Licenses will automatically renew for additional twelve (12) month terms with applicable Qualified Small Business discounts. If Customer ceases to be a Qualified Small Business, renewal pricing shall be at The Qt Company list price in effect at the time of renewal, or as agreed in writing between the parties.
-
-8. ADDITIONAL AUDIT RIGHTS. In addition to the audit rights set forth in the Agreement, The Qt Company reserves the right to audit Customer financial records in order to determine whether Customer is a Qualified Small Business.
--- /dev/null
+[gerrit]
+host=codereview.qt-project.org
+project=pyside/pyside-setup
+defaultbranch=dev
### Licensing
-PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial
+PySide6 is available under both Open Source (LGPLv3 or GPLv2 or GPLv3) and commercial
license. Using PyPi is the recommended installation source, because the
content of the wheels is valid for both cases. For more information, refer to
the [Qt Licensing page](https://www.qt.io/licensing/).
### Licensing
-PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial
+PySide6 is available under both Open Source (LGPLv3 or GPLv2 or GPLv3) and commercial
license. Using PyPi is the recommended installation source, because the
content of the wheels is valid for both cases. For more information, refer to
the [Qt Licensing page](https://www.qt.io/licensing/).
### Licensing
-PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial
+PySide6 is available under both Open Source (LGPLv3 or GPLv2 or GPLv3) and commercial
license. Using PyPi is the recommended installation source, because the
content of the wheels is valid for both cases. For more information, refer to
the [Qt Licensing page](https://www.qt.io/licensing/).
### Licensing
-PySide6 is available under both Open Source (LGPLv3/GPLv3) and commercial
+PySide6 is available under both Open Source (LGPLv3 or GPLv2 or GPLv3) and commercial
licenses. Using PyPi is the recommended installation source, because the
content of the wheels is valid for both cases. For more information, refer to
the [Qt Licensing page](https://www.qt.io/licensing/).
--- /dev/null
+# Security Policy
+
+This repository contains the source code for the PySide
+, Shiboken, and Shiboken Generator packages, which are
+under the Qt Project.
+
+The Qt Project has the security policy defined
+in the [QUIP-15](https://contribute.qt-project.org/quips/15)
+
+For reporting a vulnerability, please follow the instructions
+on [QUIP-15](https://contribute.qt-project.org/quips/15)
+before sending an email to `security at qt-project dot org`.
win32 ci
linux
darwin
-[QtMultimedia::audio_test]
- linux
- darwin
- win32
# Cannot create metal surface
[QtMultimediaWidgets::qmultimediawidgets]
darwin ci
# Open GL functions failures on macOS (2/2020)
[QtQml::qqmlnetwork_test]
linux ci # extended, see PyPy section below
-[QtWidgets::bug_750]
- darwin ci
-[QtWidgets::qpicture_test]
- darwin ci
[QtAsyncio::qasyncio_test_chain]
win32
[QtQml::bug_825]
- py3.8 # bug in typeobject::type_mro_modified, fix in 3.9
py3.9.0
py3.9.1
py3.9.2
self.setup_kwargs['long_description_content_type'] = 'text/markdown'
self.setup_kwargs['keywords'] = _pyproject_data["keywords"]
- _author, _email = _pyproject_data["authors"][0]
- self.setup_kwargs['author'] = _author
- self.setup_kwargs['author_email'] = _email
+ _author = _pyproject_data["authors"][0]
+ self.setup_kwargs['author'] = _author["name"]
+ self.setup_kwargs['author_email'] = _author["email"]
self.setup_kwargs['url'] = _pyproject_data["urls"]["Homepage"]
self.setup_kwargs['license'] = _pyproject_data["license"]["text"]
self.setup_kwargs['python_requires'] = _pyproject_data["requires-python"]
supported = get_allowed_python_versions()
this_py = sys.version_info[:2]
if this_py not in supported:
- log.error(f"Unsupported python version detected. Supported versions: {supported}")
- sys.exit(1)
+ log.warning("*" * 80)
+ log.warning(f"Unsupported Python version detected: {this_py}.")
+ log.warning("The build will probably fail.")
+ log.warning(f"Supported versions: {supported}")
+ log.warning("*" * 80)
qt_src_dir = ''
# Some libraries specific to Linux/Android from 6.8
# eg: the libav* libraries are required for the multimedia module
- if config.is_internal_pyside_build() and sys.platform != "darwin":
+ if config.is_internal_pyside_build() and (sys.platform != "darwin" or is_android):
qt_multimedia_filters = [f"lib{lib}*.so*" for lib in PYSIDE_MULTIMEDIA_LIBS]
copydir("{qt_lib_dir}", destination_qt_dir / "lib",
_filter=qt_multimedia_filters,
import functools
import os
import tempfile
+import platform
from pathlib import Path
from ..config import config
from ..options import OPTION
from ..utils import (copydir, copyfile, copy_qt_metatypes,
- download_and_extract_7z, filter_match, makefile, in_coin)
+ download_and_extract_7z, filter_match, makefile, in_coin,
+ coin_job_id)
from .. import PYSIDE, SHIBOKEN, PYSIDE_WINDOWS_BIN_TOOLS, PYSIDE_MULTIMEDIA_LIBS
if config.is_internal_shiboken_module_build():
# The C++ std library dlls need to be packaged with the
# shiboken module, because libshiboken uses C++ code.
- copy_msvc_redist_files(destination_dir)
+ download_qt_dependency_dlls(_vars, destination_dir, msvc_redist)
if config.is_internal_pyside_build() or config.is_internal_shiboken_generator_build():
copy_qt_artifacts(pyside_build, destination_qt_dir, copy_pdbs, _vars)
- copy_msvc_redist_files(destination_dir)
+ download_qt_dependency_dlls(_vars, destination_dir, msvc_redist)
# MSVC redistributable file list.
]
-def copy_msvc_redist_files(destination_dir):
- if not in_coin():
- log.info("Qt dependency DLLs (MSVC redist) will not be copied.")
- return
-
- # Make a directory where the files should be extracted.
- if not destination_dir.exists():
- destination_dir.mkdir(parents=True)
+def get_cache_dir():
+ """Return the name of a cache directory for storing artifacts for repetitive
+ runs of setup.py depending on COIN_UNIQUE_JOB_ID."""
+ job_id = coin_job_id()
+ dir = tempfile.gettempdir() + os.sep + "pyside" + job_id[0:job_id.find('-')]
+ return Path(dir)
- # Copy Qt dependency DLLs (MSVC) from PATH when building on Qt CI.
- paths = os.environ["PATH"].split(os.pathsep)
- for path in paths:
- try:
- for f in Path(path).glob("*140*.dll"):
- if f.name in msvc_redist:
- copyfile(f, Path(destination_dir) / f.name)
- msvc_redist.remove(f.name)
- if not msvc_redist:
- break
- except WindowsError:
- continue
- if msvc_redist:
- msg = "The following Qt dependency DLLs (MSVC redist) were not found: {msvc_redist}"
- raise FileNotFoundError(msg)
+def download_and_extract_7z_to_cache(url, cache_dir):
+ """Download the artifacts to the cache directory unless it exists."""
+ if not cache_dir.is_dir():
+ cache_dir.mkdir(parents=True)
+ if not list(cache_dir.glob("*.dll")):
+ download_and_extract_7z(url, cache_dir)
def download_qt_dependency_dlls(_vars, destination_qt_dir, artifacts):
log.info("Qt dependency DLLs will not be downloaded and extracted.")
return
- with tempfile.TemporaryDirectory() as temp_path:
- redist_url = "https://download.qt.io/development_releases/prebuilt/vcredist/"
- zip_file = "pyside_qt_deps_681_64_2022.7z"
- try:
- download_and_extract_7z(redist_url + zip_file, temp_path)
- except Exception as e:
- log.warning(f"Download failed: {type(e).__name__}: {e}")
- log.warning("download.qt.io is down, try with mirror")
- redist_url = "https://master.qt.io/development_releases/prebuilt/vcredist/"
- download_and_extract_7z(redist_url + zip_file, temp_path)
- copydir(temp_path, destination_qt_dir, _filter=artifacts, recursive=False, _vars=_vars)
+ cache_dir = get_cache_dir()
+ redist_url = "https://download.qt.io/development_releases/prebuilt/vcredist/"
+ zip_file = "pyside_qt_deps_684_64_2022.7z"
+ if platform.machine() == "ARM64":
+ zip_file = "pyside_qt_deps_690_arm_2022.7z"
+ try:
+ download_and_extract_7z_to_cache(redist_url + zip_file, cache_dir)
+ except Exception as e:
+ log.warning(f"Download failed: {type(e).__name__}: {e}")
+ log.warning("download.qt.io is down, try with mirror")
+ redist_url = "https://master.qt.io/development_releases/prebuilt/vcredist/"
+ download_and_extract_7z_to_cache(redist_url + zip_file, cache_dir)
+ copydir(cache_dir, destination_qt_dir, _filter=artifacts, recursive=False, _vars=_vars)
def copy_qt_artifacts(pyside_build, destination_qt_dir, copy_pdbs, _vars):
destination_qt_dir,
_vars=_vars)
- if copy_clang:
+ if copy_clang or platform.machine() == "ARM64":
+ # Qt CI is using dynamic libclang with arm config.
pyside_build.prepare_standalone_clang(is_win=True)
Modules Comma separated list of modules to be built
(for --module-subset=)
Python Python executable (Use python_d for debug builds on Windows)
+Wheel (boolean) Install via wheels instead of running setup.py install
Arbitrary keys can be defined and referenced by $(name):
JOBS_KEY = 'Jobs'
MODULES_KEY = 'Modules'
PYTHON_KEY = 'Python'
+WHEEL_KEY = 'Wheel'
DEFAULT_MODULES = "Core,Gui,Widgets,Network,Test,Qml,Quick,Multimedia,MultimediaWidgets"
DEFAULT_CONFIG_FILE = f"Modules={DEFAULT_MODULES}\n"
return config_file
-def build(target: str):
- """Run configure and build steps"""
- start_time = time.time()
+def pip_list():
+ """List installed packages from the output lines of pip (shiboken6 6.9.0a1)."""
+ result = []
+ pattern = re.compile(r"^([^\s]+)\s+\d.*$")
+ for line in run_process_output(["pip", "list"]):
+ match = pattern.search(line)
+ if match:
+ result.append(match.group(1))
+ return result
+
+def uninstall_pyside():
+ """Uninstall all PySide related packages."""
+ packages = []
+ for p in pip_list():
+ if "shiboken" in p or "PySide" in p:
+ packages.append(p)
+ if not packages or opt_dry_run:
+ return
+ yes = "Y\n" * len(packages)
+ cmd = ["pip", "uninstall"] + packages
+ with subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
+ stderr=subprocess.PIPE, text=True) as process:
+ print(process.communicate(input=yes)[0])
+
+
+def run_build(target: str):
+ """Run configure and build steps"""
arguments = []
acceleration = read_acceleration_config()
if not IS_WINDOWS and acceleration == Acceleration.INCREDIBUILD:
execute(arguments)
- elapsed_time = int(time.time() - start_time)
- print(f'--- Done({elapsed_time}s) ---')
+
+def build(skip_install: bool):
+ """Run configure and build steps"""
+ start_time = time.time()
+ use_wheel = read_bool_config(WHEEL_KEY)
+ target = "build" if use_wheel or skip_install else "install"
+ run_build(target)
+ build_time_stamp = time.time()
+ elapsed_time = int(build_time_stamp - start_time)
+ print(f"--- Build done({elapsed_time}s) ---")
+ if not use_wheel or skip_install:
+ return
+ print()
+ wheel_dir = Path.cwd() / "dist"
+ if not opt_dry_run:
+ for w in wheel_dir.glob("*.whl"):
+ w.unlink()
+ create_wheel_cmd = [read_config_python_binary(), "create_wheels.py", "--no-examples"]
+ execute(create_wheel_cmd)
+ install_cmd = ["pip", "install", "--force-reinstall"]
+ for w in wheel_dir.glob("*.whl"):
+ if not w.name.startswith("pyside6-"):
+ install_cmd.append(os.fspath(w))
+ execute(install_cmd)
+ install_time_stamp = time.time()
+ elapsed_time = int(install_time_stamp - build_time_stamp)
+ print(f"--- Install done({elapsed_time}s) ---")
def build_base_docs():
help='Run tests')
parser.add_argument('--Documentation', '-D', action='store_true',
help='Run build_base_docs')
+ parser.add_argument('--uninstall', '-U', action='store_true',
+ help='Uninstall packages')
parser.add_argument('--version', '-v', action='version', version='%(prog)s 1.0')
parser.add_argument('--verbose', '-V', action='store_true',
help='Turn off --quiet specified in build arguments')
build_mode = BuildMode.RECONFIGURE
if build_mode == BuildMode.NONE and not (options.clean or options.reset or options.pull
- or options.Documentation or options.test):
+ or options.uninstall or options.Documentation
+ or options.test):
argument_parser.print_help()
sys.exit(0)
base_dir = Path.cwd().name
+ if options.uninstall:
+ uninstall_pyside()
+
if options.clean:
run_git(['clean', '-dxf'])
run_git(['pull', '--rebase'])
if build_mode != BuildMode.NONE:
- target = 'build' if options.no_install else 'install'
- build(target)
+ build(options.no_install)
if options.Documentation:
build_base_docs()
recursive=False, _vars=_vars, force_copy_symlinks=True)
+def coin_job_id():
+ return os.environ.get("COIN_UNIQUE_JOB_ID", None)
+
+
def in_coin():
- return os.environ.get('COIN_UNIQUE_JOB_ID', None) is not None
+ return coin_job_id() is not None
def parse_modules(modules: str) -> str:
data.qml.extend(_qml)
data.extra_files.append("qmllint*")
+ # adds qmllint plugins
+ json_data_qmllint = get_module_json_data("QmlCompiler")
+ qml_lint_plugins = get_module_plugins(json_data_qmllint)
+ data.plugins += qml_lint_plugins
+
data.extra_files.append("qmlformat*")
data.extra_files.append("qmlls*")
def module_Qt3DLogic() -> ModuleData:
data = ModuleData("3DLogic", qml=["Qt3D/Logic"])
+ json_data = get_module_json_data("3DLogic")
+ data.plugins = get_module_plugins(json_data)
return data
"libQt63DQuick",
"libQt63DQuickAnimation",
"libQt63DQuickExtras",
- "libQt63DQuickExtras",
+ "libQt63DQuickLogic",
"libQt63DQuickInput",
"libQt63DQuickRender",
"libQt63DQuickScene2D",
+ "libQt63DQuickScene3D",
"libQt6Quick3DXr",
]
data = ModuleData("VirtualKeyboard")
data.plugins.append("virtualkeyboard")
data.qtlib.append("libQt6VirtualKeyboardSettings")
+ data.qtlib.append("libQt6VirtualKeyboardQml")
return data
product_dependency:
../../qt/qt5:
- ref: "480041bb0bfd400f29dd49facfaa924dacac5c03"
+ ref: "af7939f2df81369a4a6501c9b41e6e68278269eb"
dependency_source: supermodule
dependencies: [
"../../qt/qt3d",
--- /dev/null
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+# Download the file
+
+wget https://master.qt.io/development_releases/prebuilt/libclang/libclang-release_19.1.0-based-windows-vs2022_arm64.7z -o libclang.7z
+# Unzip the contents to /home/qt
+7z x libclang.7z -o/utils
+Remove-Item libclang.7z
# Copyright (C) 2024 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
# Download the file
-wget -q https://download.qt.io/development_releases/prebuilt/libclang/libclang-release_18.1.7-based-linux-Debian-11.6-gcc10.2-arm64.7z
+wget -q https://master.qt.io/development_releases/prebuilt/libclang/libclang-release_18.1.7-based-linux-Debian-11.6-gcc10.2-arm64.7z
if [ $? -ne 0 ]; then
echo "Error: Failed to download libclang archive" >&2
exit 1
- condition: property
property: host.os
equals_value: Windows
+ - condition: property
+ property: host.arch
+ not_equals_value: AARCH64
+ - type: EnvironmentVariable
+ variableName: PYTHON3_PATH
+ variableValue: "{{ index .Env \"PYTHON3.11.9-64_PATH\"}}"
+ enable_if:
+ condition: and
+ conditions:
+ - condition: property
+ property: host.os
+ equals_value: Windows
+ - condition: property
+ property: host.arch
+ equals_value: AARCH64
+ - type: EnvironmentVariable
+ variableName: TARGET_ARCHITECTURE
+ variableValue: arm64
+ enable_if:
+ condition: and
+ conditions:
+ - condition: property
+ property: target.arch
+ equals_value: AARCH64
+ - condition: property
+ property: host.os
+ equals_value: Windows
- type: EnvironmentVariable
variableName: TARGET_ARCHITECTURE
variableValue: amd64_x86
equals_value: Windows
- type: PrependToEnvironmentVariable
variableName: PATH
- variableValue: "{{.Env.PYTHON3_PATH}}/bin:"
+ variableValue: "{{.Env.PYTHON3_PATH}}:"
enable_if:
condition: property
property: host.os
condition: property
property: target.compiler
equals_value: ICC_18
+ - type: EnvironmentVariable
+ variableName: PYTHON3_PATH
+ variableValue: "{{ index .Env \"PYTHON3.10.0-64_PATH\"}}"
+ enable_if:
+ condition: and
+ conditions:
+ - condition: property
+ property: host.os
+ equals_value: Windows
+ - condition: property
+ property: host.arch
+ equals_value: X86_64
- type: EnvironmentVariable
variableName: ICC64_18_PATH # Seems a bit hard to maintain
variableValue: /opt/intel/compilers_and_libraries_2018.1.163/linux/bin/intel64:/opt/intel/bin
equals_value: AARCH64
userMessageOnFailure: >
Failed to download libclang from Qt servers
+ - type: ExecuteCommand
+ command: ["powershell", "-ExecutionPolicy", "Bypass", "-File", "coin\\fetch_libclang_arm64.ps1"]
+ maxTimeInSeconds: 14400
+ maxTimeBetweenOutput: 1200
+ enable_if:
+ condition: and
+ conditions:
+ - condition: property
+ property: host.os
+ equals_value: Windows
+ - condition: property
+ property: host.arch
+ equals_value: AARCH64
+ userMessageOnFailure: >
+ Failed to download libclang from Qt servers
- type: EnvironmentVariable
variableName: LLVM_INSTALL_DIR
variableValue: "/home/qt/libclang"
- condition: property
property: host.os
equals_value: Linux
+ - type: EnvironmentVariable
+ variableName: LLVM_INSTALL_DIR
+ variableValue: "\\utils\\libclang"
+ enable_if:
+ condition: and
+ conditions:
+ - condition: property
+ property: target.arch
+ equals_value: AARCH64
+ - condition: property
+ property: host.os
+ equals_value: Windows
- type: EnvironmentVariable
variableName: interpreter
variableValue: "python3.11"
condition: property
property: host.os
equals_value: Windows
+ - type: EnvironmentVariable
+ variableName: PYSIDE_SIGNING_DIR
+ variableValue: "{{.AgentWorkingDir}}\\pyside\\{{.Env.TESTED_MODULE_COIN}}\\build\\qfpa-p3.11\\package_for_wheels"
+ enable_if:
+ condition: and
+ conditions:
+ - condition: property
+ property: host.os
+ equals_value: Windows
+ - condition: property
+ property: host.arch
+ equals_value: AARCH64
- type: ExecuteCommand
command: "{{.Env.interpreter}} -m pip install -r requirements-coin.txt --user"
maxTimeInSeconds: 14400
userMessageOnFailure: >
Failed to install requirements-coin.txt dependencies on Windows
- type: ExecuteCommand
- command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -u coin_build_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} {{.Env.CI_USE_SCCACHE}} --instdir=\\Users\\qt\\work\\install --targetOs={{.Env.CI_OS}} --hostArch=X86_64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}} --phase=BUILD"
+ command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -u coin_build_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} {{.Env.CI_USE_SCCACHE}} --instdir=\\Users\\qt\\work\\install --targetOs={{.Env.CI_OS}} --hostArch={{.Env.HOST_ARCH_COIN}} --targetArch={{.Env.TARGET_ARCH_COIN}} --phase=BUILD"
maxTimeInSeconds: 14400
maxTimeBetweenOutput: 600
enable_if:
userMessageOnFailure: >
Failed to install requirements-coin.txt on Windows
- type: ExecuteCommand
- command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -u coin_test_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} --instdir=c:\\Users\\qt\\work\\install --targetOs={{.Env.CI_OS}} --hostArch=X86_64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}}"
+ command: "c:\\users\\qt\\MSVC.bat {{.Env.PYTHON3_PATH}}\\python.exe -u coin_test_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} --instdir=c:\\Users\\qt\\work\\install --targetOs={{.Env.CI_OS}} --hostArch={{.Env.HOST_ARCH_COIN}} --targetArch={{.Env.TARGET_ARCH_COIN}}"
maxTimeInSeconds: 14400
maxTimeBetweenOutput: 600
enable_if:
userMessageOnFailure: >
Failed to remove pyside-setup dir
- type: InstallBinaryArchive
- relativeStoragePath: "{{.Env.MODULE_ARTIFACTS_RELATIVE_STORAGE_PATH}}/artifacts.tar.gz"
+ relativeStoragePath: "{{.Env.MODULE_ARTIFACTS_RELATIVE_STORAGE_PATH}}/artifacts.tar.zst"
directory: "pyside"
maxTimeInSeconds: 1200
maxTimeBetweenOutput: 1200
del os.environ[env_var]
-def setup_virtualenv(python, exe, env, pip, log):
+def setup_virtualenv(python, exe, env, pip, log, ci):
# Within Ubuntu 24.04 one can't install anything with pip to outside of
# virtual env. Trust that we already have proper virtualenv installed.
if os.environ.get("HOST_OSVERSION_COIN") != "ubuntu_24_04":
env_path = Path(str(site.USER_BASE)) / "bin"
v_env = env_path / "virtualenv"
if sys.platform == "win32":
- env_path = os.path.join(site.USER_BASE, "Scripts")
+ if ci.TARGET_ARCH == "aarch64":
+ env_path = os.path.join(site.USER_BASE, "Python311-arm64", "Scripts")
+ else:
+ env_path = os.path.join(site.USER_BASE, "Scripts")
v_env = os.path.join(env_path, "virtualenv.exe")
try:
run_instruction([str(v_env), "--version"], "Using default virtualenv")
python = Path(get_env_or_raise("PYTHON3_PATH")) / "python.exe"
if phase == "BUILD":
- setup_virtualenv(python, exe, env, pip, log)
+ setup_virtualenv(python, exe, env, pip, log, ci)
elif phase == "TEST":
if ci.HOST_OS == "MacOS" and ci.HOST_ARCH == "ARM64":
[pip, "install", "-r", "requirements.txt"], "Failed to install dependencies"
)
else:
- setup_virtualenv(python, exe, env, pip, log)
+ setup_virtualenv(python, exe, env, pip, log, ci)
# Install distro to replace missing platform.linux_distribution() in python3.8
run_instruction([pip, "install", "distro"], "Failed to install distro")
- condition: property # Windows on Arm
property: target.arch
not_equals_value: ARM64
- - condition: property # Windows on Arm host build
- property: target.arch
- not_equals_value: AARCH64
- condition: property
property: features
not_contains_value: DebianPackaging
# We know the CI builds universal2 wheels
_tag = f"macosx_{target}_universal2"
elif _os == "win32":
- win_arch = platform.architecture()[0]
- msvc_arch = "x86" if win_arch.startswith("32") else "amd64"
+ win_arch = platform.machine()
+ msvc_arch = "arm64" if win_arch.startswith("ARM64") else "amd64"
_tag = f"win_{msvc_arch}"
return _tag
--- /dev/null
+Qt for Python 6.8.3 is a bug-fix release.
+
+For more details, refer to the online documentation included in this
+distribution. The documentation is also available online:
+
+https://doc.qt.io/qtforpython/
+
+Some of the changes listed in this file include issue tracking numbers
+corresponding to tasks in the Qt Bug Tracker:
+
+https://bugreports.qt.io/
+
+Each of these identifiers can be entered in the bug tracker to obtain more
+information about a particular change.
+
+****************************************************************************
+* PySide6 *
+****************************************************************************
+
+ - [PYSIDE-1735] Most enumerations are now fully qualified in documentation
+ and translated code snippets. Enum values automatically
+ converted to "None_" are also correctly documented.
+ - [PYSIDE-2846] The type annotation of the Slot() decorator has been fixed.
+ - [PYSIDE-2997] Type hints: The signature of QMessageBox.information()
+ has been fixed.
+ - [PYSIDE-3001] QtAsyncio: The error messages printed on an exceptions
+ in tasks have been improved.
+ - [PYSIDE-3002] QPaintEngine's virtual functions taking a
+ C-style array of geometry primitives have been fixed.
+ - [PYSIDE-3003] Type hints: A bug causing overloads to be omitted has
+ been fixed.
+ - [PYSIDE-3012] Type hints: Since using Callable, Iterable and Sequence from
+ typing is deprecated, they are imported from collections.abc.
+ - [PYSIDE-3012] Type hints: Object inheritance has been removed from classes.
+ - [PYSIDE-3013] QFont.Tag(str), QFont.Tag.fromString(), QFont.Tag.fromValue()
+ have been added.
+ - [PYSIDE-3014] QAbstractSpinBox.fixup() has been fixed.
+ - [PYSIDE-3017] The overloads of QCanDbcFileParser.parse() have been fixed.
+ - [PYSIDE-3020] A bug disconnecting a string-based connection by passing a
+ callable has been fixed.
+ - [QTBUG-72968] The type of the "result" parameter of the native event
+ filters has been changed to "qintptr" for Qt 6.
+
+****************************************************************************
+* Shiboken6 *
+****************************************************************************
+
+ - Warnings about rejected functions/fields have been removed or redirected
+ to the log files.
+ - [PYSIDE-2701] The size of the generated modules has been reduced by
+ factoring out common code from the code generated for
+ virtual functions.
--- /dev/null
+Qt for Python 6.9.0 is a minor release.
+
+For more details, refer to the online documentation included in this
+distribution. The documentation is also available online:
+
+https://doc.qt.io/qtforpython/
+
+Some of the changes listed in this file include issue tracking numbers
+corresponding to tasks in the Qt Bug Tracker:
+
+https://bugreports.qt.io/
+
+Each of these identifiers can be entered in the bug tracker to obtain more
+information about a particular change.
+
+****************************************************************************
+* PySide6 *
+****************************************************************************
+
+ - The usage of macros from CPython (limited API) has been reduced in favor
+ of calling functions directly.
+ - Support for std::chrono::milliseconds has been added.
+ - Windows ARM64 was added as a new supported platform (technical preview)
+ - [PYSIDE-862] Support for QtRemoteObjects has been extended.
+ - [PYSIDE-1057] A memory leak connecting to signals has been fixed.
+ - [PYSIDE-1277] A bug connecting signals by QMetaMethod has been fixed.
+ - [PYSIDE-1735] Most enumerations are now fully qualified in documentation
+ and translated code snippets. Enum values automatically
+ converted to "None_" are also correctly documented.
+ - [PYSIDE-2714] Qt Creator 17.x+ and PySide tools now support projects that
+ have a pyproject.toml instead of a *.pyproject
+ - [PYSIDE-2846] The type annotation of the Slot() decorator has been fixed.
+ - [PYSIDE-2966] A bug making it possible to instantiate non-constructible
+ classes and namespaces has been fixed.
+ - [PYSIDE-2891] A bug in signature handling affecting 32bit builds
+ has been fixed.
+ - [PYSIDE-2958] Building against unsupported python versions has been
+ enabled.
+ - [PYSIDE-2997] Type hints: The signature of QMessageBox.information()
+ has been fixed.
+ - [PYSIDE-3001] QtAsyncio: The error messages printed on an exceptions
+ in tasks have been improved.
+ - [PYSIDE-3002] QPaintEngine's virtual functions taking a
+ C-style array of geometry primitives have been fixed.
+ - [PYSIDE-3003] Type hints: A bug causing overloads to be omitted has
+ been fixed.
+ - [PYSIDE-3004] A crash in slots when receiving an object-type parameter
+ passed by const-ref has been fixed.
+ - [PYSIDE-3005] A bug affecting
+ QOpenGLShaderProgram.setUniformValueArray(int,float*,int,int)
+ has been fixed.
+ - [PYSIDE-3012] Type hints: Since using Callable, Iterable and Sequence from
+ typing is deprecated, they are imported from collections.abc.
+ - [PYSIDE-3012] Type hints: Object inheritance has been removed from classes.
+ - [PYSIDE-3013] QFont.Tag(str), QFont.Tag.fromString(), QFont.Tag.fromValue()
+ have been added.
+ - [PYSIDE-3014] QAbstractSpinBox.fixup() has been fixed.
+ - [PYSIDE-3017] The overloads of QCanDbcFileParser.parse() have been fixed.
+ - [PYSIDE-3020] A bug disconnecting a string-based connection by passing a
+ callable has been fixed.
+ - [QTBUG-72968] The type of the "result" parameter of the native event
+ filters has been changed to "qintptr" for Qt 6.
+ - [QTBUG-108199] PySide has been adapted to the deprecation of Qt::TimeSpec
+ in Qt.
+
+****************************************************************************
+* Shiboken6 *
+****************************************************************************
+
+ - Helper class AutoArrayPointer has been renamed to ArrayPointer. A
+ convenience typedef is provided.
+ - It is now possible to use file snippets for XML template content.
+ - Warnings about rejected functions/fields have been removed or redirected
+ to log files to reduce clutter.
+ - An additional log file, mjb_shiboken.log has been introduced which
+ contains informational messages about the types encountered.
+ - [PYSIDE-454] It is now possible to exclude smart pointer instantiations
+ from underlying base modules to prevent symbol clashes.
+ - [PYSIDE-2701] The size of the generated modules has been reduced by
+ applying several optimizations to the code generated for
+ virtual functions.
+ - [PYSIDE-2701] Function modifications are now correctly inherited also in
+ case of multiple inheritance.
+ - [PYSIDE-2986] shiboken6 can now generate doc strings for classes from
+ injected documentation snippets.
+ - [PYSIDE-3004] The detection of copy constructibility of value type
+ classes has been improved using functionality from Clang.
+ New typesystem attributes have been introduced to enable
+ overriding the detection.
+ - [PYSIDE-3004] The handling of move only value types has been improved.
+ - [PYSIDE-3004] A documentation page about value versus objects has been
+ added.
+ - [PYSIDE-3004] Non-default constructible value types can now be
+ passed by non-const reference.
+ - [PYSIDE-3004] Warnings about special types are now printed, particularly
+ about object types that could be value types.
+ - [QTBUG-133704] It is now possible to specify in the type system whether
+ Qt meta type registration code should be generated for enums.
--- /dev/null
+Qt for Python 6.9.1 is a bug-fix release.
+
+For more details, refer to the online documentation included in this
+distribution. The documentation is also available online:
+
+https://doc.qt.io/qtforpython/
+
+Some of the changes listed in this file include issue tracking numbers
+corresponding to tasks in the Qt Bug Tracker:
+
+https://bugreports.qt.io/
+
+Each of these identifiers can be entered in the bug tracker to obtain more
+information about a particular change.
+
+****************************************************************************
+* PySide6 *
+****************************************************************************
+
+ - [PYSIDE-841] An example showing an audio graph using Qt Graphs has been
+ added.
+ - [PYSIDE-2193] A crash retrieving a Python type QObject property
+ (encapsulated in a QVariant) been fixed.
+ - [PYSIDE-3052] class QMessageLogger has been added for handling debug,
+ info, warning, critical, and fatal messages,
+ including support for QLoggingCategory.
+ - [PYSIDE-3012] type hints: The type signature for the Property class
+ has been fixed.
+ - [PYSIDE-3021] type-hints: The QMessagebox annotations has been fixed.
+ - [PYSIDE-3029] type-hints: The hints for properties on __init__ functions
+ has been fixed.
+ - [PYSIDE-3041] type hints: The str parameter of QLineEdit.setText() has
+ been made optional.
+ - [PYSIDE-3050] type hints: Type signature for
+ QProgressDialog.setCancelButton has been fixed.
+ - [PYSIDE-3055] type hints: Type signature for QTabBar.setTabButton has
+ been fixed.
+ - [PYSIDE-3056] type hints: Type signature for qtTrId has been fixed.
+ - [PYSIDE-3057] type hints: Type signature for QTreeWidget.setItemWidget
+ has been fixed.
+ - [PYSIDE-3058] type hints: Return value for QTreeWidget.topLevelItem and
+ QTreeWidget.takeTopLevelItem has been fixed.
+ - [PYSIDE-3059] type hints: The type signature for QObject class has been
+ fixed.
+ - [PYSIDE-3061] The building of .pyi files in debug mode on Windows has
+ been fixed.
+ - [PYSIDE-3067] A crash when entering a Qt message handler with a Python
+ error set has been fixed.
+ - [PYSIDE-3069] A crash retrieving a QGraphicsProxyObject from a QVariant
+ has been fixed.
+ - [PYSIDE-3078] type hints: The parent widget parameter of the
+ QInputDialog get() methods has been made optional.
+ - [PYSIDE-3087] The dependency of pyside6-project on tomlkit has been
+ removed.
+ - [PYSIDE-3089] An error in pyside6-metaobjectdump when encountering
+ @Slot(result=None) has been fixed.
+
+****************************************************************************
+* Shiboken6 *
+****************************************************************************
+
+ - [PYSIDE-3081] A bug in the clang parser causing errors when parsing a
+ lambda contained in a function parameter default value has
+ been fixed.
--- /dev/null
+Qt for Python 6.9.2 is a bug-fix release.
+
+For more details, refer to the online documentation included in this
+distribution. The documentation is also available online:
+
+https://doc.qt.io/qtforpython/
+
+Some of the changes listed in this file include issue tracking numbers
+corresponding to tasks in the Qt Bug Tracker:
+
+https://bugreports.qt.io/
+
+Each of these identifiers can be entered in the bug tracker to obtain more
+information about a particular change.
+
+****************************************************************************
+* PySide6 *
+****************************************************************************
+
+ - [PYSIDE-1612] Android Deployment: Installing packages is no longer forced.
+ - [PYSIDE-1612] Desktop Deployment: A warning for Qt resource files has
+ been fixed.
+ - [PYSIDE-1612] Desktop Deployment: Nuitka has been upgraded to 2.7.11.
+ - [PYSIDE-2846] type hints: The mypy version has been increased.
+ - [PYSIDE-2846] type hints: Signature warnings about QObject properties of
+ unknown type passed to the constructor have been fixed.
+ - [PYSIDE-2938] The MSVC runtime libraries bundled in Windows wheels
+ have been updated.
+ - [PYSIDE-3095] pyside6-project lupdate now supports specifying
+ subdirectories in .ts file names.
+ - [PYSIDE-3115] A bug affecting QVariant conversion of Python classes
+ inheriting QGraphicsProxyObject has been fixed.
+ - [PYSIDE-3119] A SECURITY.md document required for Github has been added.
+ - [PYSIDE-3124] Documentation about thread affinity has been added
+ to the Signals and Slot tutorial.
+ - [PYSIDE-3127] A bug occurring when choosing a camera in the camera
+ example has been fixed.
+ - [PYSIDE-3132] A crash calling setItemDelegateForColumn/Row()
+ repeatedly has been fixed.
+ - [PYSIDE-3133] A crash on conversion errors when parsing keyword
+ arguments has been fixed.
+ - [PYSIDE-3135] type hints: typing.Self is no longer modified in
+ versions < 3.11.
+ - [PYSIDE-3139] type hints: The return types of QGuiApplication.screenAt()
+ and QGuiApplication.modalWindow() have been fixed.
+ - [PYSIDE-3146] Deployment: Values generated into pysidedeploy.spec are
+ now sorted.
+ - [PYSIDE-3147] Initial adaptations for the upcoming Python version 3.14
+ have been done.
+ - [PYSIDE-3148] A memory corruption occurring when connecting several
+ signals to one non-QObject receiver has been fixed.
+
+****************************************************************************
+* Shiboken6 *
+****************************************************************************
+
+ - [PYSIDE-3105] Cross compilation support has been improved.
+ - [PYSIDE-3144] A crash occurring when no source class can be found for
+ typedef typesystem entries has been fixed.
class DonutBreakdownChart(QChart):
def __init__(self, parent=None):
super().__init__(QChart.ChartTypeCartesian,
- parent, Qt.WindowFlags())
+ parent, Qt.WindowFlags(0))
self.main_series = QPieSeries()
self.main_series.setPieSize(0.7)
self.addSeries(self.main_series)
class Chart(QChart):
def __init__(self, parent=None):
- super().__init__(QChart.ChartTypeCartesian, parent, Qt.WindowFlags())
+ super().__init__(QChart.ChartTypeCartesian, parent, Qt.WindowFlags(0))
self._timer = QTimer()
self._series = QSplineSeries(self)
self._titles = []
self._axis_x.setRange("Jan", "Jun")
self._axis_y = QValueAxis()
- self.chart.addAxis(self._axis_x, Qt.AlignLeft)
+ self.chart.addAxis(self._axis_y, Qt.AlignLeft)
self._line_series.attachAxis(self._axis_y)
self._bar_series.attachAxis(self._axis_y)
self._axis_y.setRange(0, 20)
--- /dev/null
+``ImageViewer`` displays images as supported by ``QImageReader``, using
+a QLabel.
+
+In the constructor, we increase the allocation limit of ``QImageReader`` to
+allow for larger photos.
+
+In the ``openFile()`` function, we load the image and determine its size.
+If it is larger than the screen, we downscale it to screen size, maintaining
+the aspect ratio. This calculation has to be done in native pixels, and
+the device pixel ratio needs to be set on the resulting pixmap for it to
+appear crisp.
"main.py",
"mainwindow.py",
"mainwindow.ui",
+ "imageviewer/imageviewer.py",
"jsonviewer/jsonviewer.py",
"pdfviewer/pdfviewer.py",
"pdfviewer/zoomselector.py",
--- /dev/null
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+from __future__ import annotations
+
+import math
+
+from PySide6.QtWidgets import QLabel
+from PySide6.QtCore import Qt, QDir, QSizeF
+from PySide6.QtGui import (QPixmap, QImageReader, QIcon, QKeySequence,
+ QGuiApplication, QColorSpace, QPainter, QAction)
+
+from abstractviewer import AbstractViewer
+
+
+def imageFormats():
+ result = []
+ all_formats = QImageReader.supportedImageFormats()
+
+ for format_bytes in all_formats:
+ format_str = bytes(format_bytes).decode("utf-8") # Convert QByteArray to str
+ if format_str not in ["tif", "cur"]: # Exclude duplicate/non-existent formats
+ result.append(f"image/{format_str}")
+
+ return result
+
+
+def msgOpen(name, image):
+ description = image.colorSpace().description() if image.colorSpace().isValid() else "unknown"
+ return 'Opened "{0}", {1}x{2}, Depth: {3} ({4})'.format(
+ QDir.toNativeSeparators(name),
+ image.width(),
+ image.height(),
+ image.depth(),
+ description
+ )
+
+
+class ImageViewer(AbstractViewer):
+
+ def __init__(self):
+ super().__init__()
+
+ self.formats = imageFormats()
+ self.uiInitialized.connect(self.setupImageUi)
+ QImageReader.setAllocationLimit(1024) # MB
+
+ def init(self, file, parent, mainWindow):
+ self.image_label = QLabel(parent)
+ self.image_label.setFrameShape(QLabel.Box)
+ self.image_label.setAlignment(Qt.AlignCenter)
+ self.image_label.setScaledContents(True)
+
+ # AbstractViewer.init(file, self.image_label, mainWindow)
+ super().init(file, self.image_label, mainWindow)
+
+ self.tool_bar = self.addToolBar(self.tr("Images"))
+
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomIn,
+ QIcon(":/demos/documentviewer/images/zoom-in.png"))
+ self.zoom_in_act = QAction(icon, "Zoom &In", self)
+ self.zoom_in_act.setShortcut(QKeySequence.StandardKey.ZoomIn)
+ self.zoom_in_act.triggered.connect(self.zoomIn)
+ self.tool_bar.addAction(self.zoom_in_act)
+
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomOut,
+ QIcon(":/demos/documentviewer/images/zoom-out.png"))
+ self.zoom_out_act = QAction(icon, "Zoom &Out", self)
+ self.zoom_out_act.setShortcut(QKeySequence.StandardKey.ZoomOut)
+ self.zoom_out_act.triggered.connect(self.zoomOut)
+ self.tool_bar.addAction(self.zoom_out_act)
+
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomFitBest,
+ QIcon(":/demos/documentviewer/images/zoom-fit-best.png"))
+ self.reset_zoom_act = QAction(icon, "Reset Zoom", self)
+ self.reset_zoom_act.setShortcut(QKeySequence
+ (Qt.KeyboardModifier.ControlModifier | Qt.Key.Key_0))
+ self.reset_zoom_act.triggered.connect(self.resetZoom)
+ self.tool_bar.addAction(self.reset_zoom_act)
+
+ def supportedMimeTypes(self):
+ return self.formats
+
+ def clear(self):
+ self.image_label.setPixmap(QPixmap())
+ self.max_scale_factor = self.min_scale_factor = 1
+ self.initial_scale_factor = self.scale_factor = 1
+
+ def setupImageUi(self):
+ self.openFile()
+
+ def openFile(self):
+
+ QGuiApplication.setOverrideCursor(Qt.WaitCursor)
+
+ name = self._file.fileName()
+ reader = QImageReader(name)
+ orig_image = reader.read()
+
+ if orig_image.isNull():
+ self.statusMessage(f"Cannot read file {name}:\n{reader.errorString()}", "open")
+ self.disablePrinting()
+ QGuiApplication.restoreOverrideCursor()
+ return
+
+ self.clear()
+
+ if orig_image.colorSpace().isValid():
+ image = orig_image.convertedToColorSpace(QColorSpace.SRgb)
+ else:
+ image = orig_image
+
+ device_pixel_ratio = self.image_label.devicePixelRatioF()
+ self.image_size = QSizeF(image.size()) / device_pixel_ratio
+
+ pixmap = QPixmap.fromImage(image)
+ pixmap.setDevicePixelRatio(device_pixel_ratio)
+ self.image_label.setPixmap(pixmap)
+
+ target_size = self.image_label.parentWidget().size()
+ if (self.image_size.width() > target_size.width()
+ or self.image_size.height() > target_size.height()):
+ self.initial_scale_factor = min(target_size.width() / self.image_size.width(),
+ target_size.height() / self.image_size.height())
+
+ self.max_scale_factor = 3 * self.initial_scale_factor
+ self.min_scale_factor = self.initial_scale_factor / 3
+ self.doSetScaleFactor(self.initial_scale_factor)
+
+ self.statusMessage(msgOpen(name, orig_image))
+ QGuiApplication.restoreOverrideCursor()
+
+ self.maybeEnablePrinting()
+
+ def setScaleFactor(self, scaleFactor):
+ if not math.isclose(self.scale_factor, scaleFactor):
+ self.doSetScaleFactor(scaleFactor)
+
+ def doSetScaleFactor(self, scaleFactor):
+ self.scale_factor = scaleFactor
+ label_size = (self.image_size * self.scale_factor).toSize()
+ self.image_label.setFixedSize(label_size)
+ self.enableZoomActions()
+
+ def zoomIn(self):
+ self.setScaleFactor(self.scale_factor * 1.25)
+
+ def zoomOut(self):
+ self.setScaleFactor(self.scale_factor * 0.8)
+
+ def resetZoom(self):
+ self.setScaleFactor(self.initial_scale_factor)
+
+ def hasContent(self):
+ return not self.image_label.pixmap().isNull()
+
+ def enableZoomActions(self):
+ self.reset_zoom_act.setEnabled(not math.isclose(self.scale_factor,
+ self.initial_scale_factor))
+ self.zoom_in_act.setEnabled(self.scale_factor < self.max_scale_factor)
+ self.zoom_out_act.setEnabled(self.scale_factor > self.min_scale_factor)
+
+ def printDocument(self, printer):
+ if not self.hasContent():
+ return
+
+ painter = QPainter(printer)
+ pixmap = self.image_label.pixmap()
+ rect = painter.viewport()
+ size = pixmap.size()
+ size.scale(rect.size(), Qt.KeepAspectRatio)
+ painter.setViewport(rect.x(), rect.y(), size.width(), size.height())
+ painter.setWindow(pixmap.rect())
+ painter.drawPixmap(0, 0, pixmap)
actionZoomIn.setToolTip("Increase zoom level")
actionZoomIn.triggered.connect(self.onActionZoomInTriggered)
- icon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomIn,
+ icon = QIcon.fromTheme(QIcon.ThemeIcon.ZoomOut,
QIcon(":/demos/documentviewer/images/zoom-out.png"))
actionZoomOut = self._toolBar.addAction(icon, "Zoom out", QKeySequence.StandardKey.ZoomOut)
actionZoomOut.setToolTip("Decrease zoom level")
from txtviewer.txtviewer import TxtViewer
from jsonviewer.jsonviewer import JsonViewer
from pdfviewer.pdfviewer import PdfViewer
+from imageviewer.imageviewer import ImageViewer
class DefaultPolicy(Enum):
self._displayWidget = displayWidget
self._mainWindow = mainWindow
self._mimeTypes = []
- for v in [PdfViewer(), JsonViewer(), TxtViewer()]:
+ for v in [PdfViewer(), JsonViewer(), TxtViewer(), ImageViewer()]:
self._viewers[v.viewerName()] = v
if v.isDefaultViewer():
self._defaultViewer = v
--- /dev/null
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtGraphs
+
+ApplicationWindow {
+ visible: true
+ width: 1000
+ height: 800
+ title: "Data from the microphone (" + device_name + ")"
+
+ GraphsView {
+ id: graph
+ anchors.fill: parent
+
+ LineSeries {
+ id: audio_series
+ width: 2
+ color: "#007acc"
+ }
+
+ axisX: ValueAxis {
+ min: 0
+ max: 2000
+ tickInterval : 500
+ labelFormat: "%g"
+ titleText: "Samples"
+ }
+
+ axisY: ValueAxis {
+ min: -1
+ max: 1
+ tickInterval : 0.5
+ labelFormat: "%0.1f"
+ titleText: "Audio level"
+ }
+ }
+
+ Connections {
+ target: audio_bridge
+ function onDataUpdated(buffer) {
+ audio_series.clear()
+ for (let i = 0; i < buffer.length; ++i) {
+ audio_series.append(buffer[i])
+ }
+ }
+ }
+}
--- /dev/null
+module GraphsAudio
+Main 1.0 Main.qml
--- /dev/null
+GraphsAudio Example
+===================
+
+This example shows the drawing of dynamic data (microphone input) using QtGraphs and Qml.
+
+.. image:: graphsaudio.webp
+ :width: 400
+ :alt: GraphsAudio Screenshot
--- /dev/null
+{
+ "files": ["main.py", "GraphsAudio/Main.qml", "GraphsAudio/qmldir"]
+}
--- /dev/null
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from PySide6.QtCore import QObject, QPointF, Slot, Signal
+from PySide6.QtMultimedia import QAudioFormat, QAudioSource, QMediaDevices
+from PySide6.QtWidgets import QMessageBox
+from PySide6.QtQml import QQmlApplicationEngine
+from PySide6.QtGui import QGuiApplication
+
+
+SAMPLE_COUNT = 2000
+RESOLUTION = 4
+
+
+class Audio(QObject):
+ dataUpdated = Signal(list)
+
+ def __init__(self, device):
+ super().__init__()
+
+ format_audio = QAudioFormat()
+ format_audio.setSampleRate(8000)
+ format_audio.setChannelCount(1)
+ format_audio.setSampleFormat(QAudioFormat.UInt8)
+
+ self.device_name = device.description()
+
+ self._audio_input = QAudioSource(device, format_audio, self)
+ self._io_device = self._audio_input.start()
+ self._io_device.readyRead.connect(self._readyRead)
+
+ self._buffer = [QPointF(x, 0) for x in range(SAMPLE_COUNT)]
+
+ def closeEvent(self, event):
+ if self._audio_input is not None:
+ self._audio_input.stop()
+ event.accept()
+
+ @Slot()
+ def _readyRead(self):
+ data = self._io_device.readAll()
+ available_samples = data.size() // RESOLUTION
+ start = 0
+ if (available_samples < SAMPLE_COUNT):
+ start = SAMPLE_COUNT - available_samples
+ for s in range(start):
+ self._buffer[s].setY(self._buffer[s + available_samples].y())
+
+ data_index = 0
+ for s in range(start, SAMPLE_COUNT):
+ value = (ord(data[data_index]) - 128) / 128
+ self._buffer[s].setY(value)
+ data_index = data_index + RESOLUTION
+
+ self.dataUpdated.emit(self._buffer)
+
+
+if __name__ == '__main__':
+ app = QGuiApplication(sys.argv)
+ engine = QQmlApplicationEngine()
+
+ input_devices = QMediaDevices.audioInputs()
+ if not input_devices:
+ QMessageBox.warning(None, "audio", "There is no audio input device available.")
+ sys.exit(-1)
+
+ audio_bridge = Audio(input_devices[0])
+ engine.rootContext().setContextProperty("audio_bridge", audio_bridge)
+
+ device = input_devices[0]
+ device_name = device.description()
+ engine.rootContext().setContextProperty("device_name", device_name)
+
+ engine.addImportPath(Path(__file__).parent)
+ engine.loadFromModule("GraphsAudio", "Main")
+
+ sys.exit(app.exec())
self._position = {}
self._topographicSeries = None
self._minHeight = 0.0
+ self._height_adjustment = 5.0
self.setDrawMode(QSurface3DSeries.DrawFlag.DrawSurface)
self.setShading(QSurface3DSeries.Shading.Flat)
self.setVisible(False)
srcRow = srcArray[i]
for j in range(startX, endX):
pos = srcRow.at(j).position()
- pos.setY(pos.y() + 0.1)
+ pos.setY(pos.y() + self._height_adjustment)
item = QSurfaceDataItem(QVector3D(pos))
newRow.append(item)
dataArray.append(newRow)
self.setBaseGradient(gr)
self.setColorStyle(QGraphsTheme.ColorStyle.RangeGradient)
+
+ self.handle_zoom_change(ratio)
+
+ def handle_zoom_change(self, zoom):
+ self._height_adjustment = (1.2 - zoom) * 10.0
from enum import Enum
from math import sin, cos, degrees, sqrt
-from PySide6.QtCore import QObject, Signal, Slot, Qt
+from PySide6.QtCore import QObject, Signal, Slot, Qt, QRandomGenerator
from PySide6.QtGui import QVector2D, QVector3D
from PySide6.QtGraphs import (QAbstract3DSeries,
QScatterDataItem, QScatterDataProxy,
class ScatterDataModifier(QObject):
- backgroundEnabledChanged = Signal(bool)
+ backgroundVisibleChanged = Signal(bool)
gridVisibleChanged = Signal(bool)
shadowQualityChanged = Signal(int)
self._itemCount = LOWER_NUMBER_OF_ITEMS
self._CURVE_DIVIDER = LOWER_CURVE_DIVIDER
- self._graph.activeTheme().setTheme(QGraphsTheme.Theme.MixSeries)
- self._graph.activeTheme().setColorScheme(QGraphsTheme.ColorScheme.Dark)
self._graph.setShadowQuality(QtGraphs3D.ShadowQuality.SoftHigh)
self._graph.setCameraPreset(QtGraphs3D.CameraPreset.Front)
self._graph.setCameraZoomLevel(80.0)
+ self._graph.activeTheme().setTheme(QGraphsTheme.Theme.MixSeries)
+ self._graph.activeTheme().setColorScheme(QGraphsTheme.ColorScheme.Dark)
self._proxy = QScatterDataProxy()
self._series = QScatter3DSeries(self._proxy)
def changeTheme(self, theme):
currentTheme = self._graph.activeTheme()
currentTheme.setTheme(QGraphsTheme.Theme(theme))
- self.backgroundEnabledChanged.emit(currentTheme.isPlotAreaBackgroundVisible())
+ self.backgroundVisibleChanged.emit(currentTheme.isPlotAreaBackgroundVisible())
self.gridVisibleChanged.emit(currentTheme.isGridVisible())
@Slot()
def shadowQualityUpdatedByVisual(self, sq):
self.shadowQualityChanged.emit(sq.value)
- @Slot(int)
- def changeShadowQuality(self, quality):
- sq = QtGraphs3D.ShadowQuality(quality)
- self._graph.setShadowQuality(sq)
-
- @Slot(int)
- def setPlotAreaBackgroundVisible(self, state):
- enabled = state == Qt.CheckState.Checked
- self._graph.activeTheme().setPlotAreaBackgroundVisible(enabled)
-
- @Slot(int)
- def setGridVisible(self, state):
- self._graph.activeTheme().setGridVisible(state == Qt.Checked.value)
-
- @Slot()
- def toggleItemCount(self):
- if self._itemCount == NUMBER_OF_ITEMS:
- self._itemCount = LOWER_NUMBER_OF_ITEMS
- self._CURVE_DIVIDER = LOWER_CURVE_DIVIDER
- else:
- self._itemCount = NUMBER_OF_ITEMS
- self._CURVE_DIVIDER = CURVE_DIVIDER
-
- self._graph.seriesList()[0].dataProxy().resetArray([])
- self.addData()
-
- @Slot()
- def toggleRanges(self):
- if not self._autoAdjust:
- self._graph.axisX().setAutoAdjustRange(True)
- self._graph.axisZ().setAutoAdjustRange(True)
- self._dragSpeedModifier = 1.5
- self._autoAdjust = True
- else:
- self._graph.axisX().setRange(-10.0, 10.0)
- self._graph.axisZ().setRange(-10.0, 10.0)
- self._dragSpeedModifier = float(15)
- self._autoAdjust = False
-
@Slot(QtGraphs3D.ElementType)
def handleElementSelected(self, type):
if type == QtGraphs3D.ElementType.AxisXLabel:
# No need to use adjusted y move here
distance = move.y() / self._dragSpeedModifier
axis.setRange(axis.min() + distance, axis.max() + distance)
+
+ @Slot(int)
+ def changeShadowQuality(self, quality):
+ sq = QtGraphs3D.ShadowQuality(quality)
+ self._graph.setShadowQuality(sq)
+
+ @Slot(int)
+ def setBackgroundVisible(self, state):
+ enabled = state == Qt.CheckState.Checked
+ self._graph.activeTheme().setPlotAreaBackgroundVisible(enabled)
+
+ @Slot(int)
+ def setGridVisible(self, state):
+ self._graph.activeTheme().setGridVisible(state == Qt.Checked.value)
+
+ @Slot()
+ def toggleItemCount(self):
+ if self._itemCount == NUMBER_OF_ITEMS:
+ self._itemCount = LOWER_NUMBER_OF_ITEMS
+ self._CURVE_DIVIDER = LOWER_CURVE_DIVIDER
+ else:
+ self._itemCount = NUMBER_OF_ITEMS
+ self._CURVE_DIVIDER = CURVE_DIVIDER
+
+ self._graph.seriesList()[0].dataProxy().resetArray([])
+ self.addData()
+
+ @Slot()
+ def toggleRanges(self):
+ if not self._autoAdjust:
+ self._graph.axisX().setAutoAdjustRange(True)
+ self._graph.axisZ().setAutoAdjustRange(True)
+ self._dragSpeedModifier = 1.5
+ self._autoAdjust = True
+ else:
+ self._graph.axisX().setRange(-10.0, 10.0)
+ self._graph.axisZ().setRange(-10.0, 10.0)
+ self._dragSpeedModifier = float(15)
+ self._autoAdjust = False
+
+ def adjust_minimum_range(self, range):
+ if self._itemCount == LOWER_NUMBER_OF_ITEMS:
+ range *= 1.45
+ else:
+ range *= 4.95
+
+ self._graph.axisX().setMin(range)
+ self._graph.axisZ().setMin(range)
+ self._autoAdjust = False
+
+ def adjust_maximum_range(self, range):
+ if self._itemCount == LOWER_NUMBER_OF_ITEMS:
+ range *= 1.45
+ else:
+ range *= 4.95
+
+ self._graph.axisX().setMax(range)
+ self._graph.axisZ().setMax(range)
+ self._autoAdjust = False
+
+ def rand_vector() -> QVector3D:
+ generator = QRandomGenerator.global_()
+ x = float(generator.bounded(100)) / 2.0 - float(generator.bounded(100)) / 2.0
+ y = float(generator.bounded(100)) / 100.0 - float(generator.bounded(100)) / 100.0
+ z = float(generator.bounded(100)) / 2.0 - float(generator.bounded(100)) / 2.0
+ return QVector3D(x, y, z)
from PySide6.QtCore import QObject, QSize, Qt
from PySide6.QtWidgets import (QCheckBox, QComboBox, QCommandLinkButton,
QLabel, QHBoxLayout, QSizePolicy,
- QVBoxLayout, QWidget, )
+ QVBoxLayout, QWidget, QSlider)
from PySide6.QtQuickWidgets import QQuickWidget
from PySide6.QtGraphs import QAbstract3DSeries
from PySide6.QtGraphsWidgets import Q3DScatterWidgetItem
itemCountButton.setDescription("Switch between 900 and 10000 data points")
itemCountButton.setIconSize(QSize(0, 0))
- rangeButton = QCommandLinkButton(self._scatterWidget)
- rangeButton.setText("Toggle axis ranges")
- rangeButton.setDescription("Switch between automatic axis ranges and preset ranges")
- rangeButton.setIconSize(QSize(0, 0))
+ range_min_slider = QSlider(Qt.Horizontal, self._scatterWidget)
+ range_min_slider.setMinimum(-10)
+ range_min_slider.setMaximum(1)
+ range_min_slider.setValue(-10)
+
+ range_max_slider = QSlider(Qt.Horizontal, self._scatterWidget)
+ range_max_slider.setMinimum(1)
+ range_max_slider.setMaximum(10)
+ range_max_slider.setValue(10)
backgroundCheckBox = QCheckBox(self._scatterWidget)
backgroundCheckBox.setText("Show graph background")
vLayout.addWidget(cameraButton)
vLayout.addWidget(itemCountButton)
- vLayout.addWidget(rangeButton)
+ vLayout.addWidget(range_min_slider)
+ vLayout.addWidget(range_max_slider)
vLayout.addWidget(backgroundCheckBox)
vLayout.addWidget(gridCheckBox)
vLayout.addWidget(smoothCheckBox)
cameraButton.clicked.connect(modifier.changePresetCamera)
itemCountButton.clicked.connect(modifier.toggleItemCount)
- rangeButton.clicked.connect(modifier.toggleRanges)
+ range_min_slider.valueChanged.connect(modifier.adjust_minimum_range)
+ range_max_slider.valueChanged.connect(modifier.adjust_maximum_range)
- backgroundCheckBox.checkStateChanged.connect(modifier.setPlotAreaBackgroundVisible)
+ backgroundCheckBox.checkStateChanged.connect(modifier.setBackgroundVisible)
gridCheckBox.checkStateChanged.connect(modifier.setGridVisible)
smoothCheckBox.checkStateChanged.connect(modifier.setSmoothDots)
- modifier.backgroundEnabledChanged.connect(backgroundCheckBox.setChecked)
+ modifier.backgroundVisibleChanged.connect(backgroundCheckBox.setChecked)
modifier.gridVisibleChanged.connect(gridCheckBox.setChecked)
itemStyleList.currentIndexChanged.connect(modifier.changeStyle)
import sys
from PySide6.QtCore import QPoint, QTimer, QTime, Qt
-from PySide6.QtGui import QGuiApplication, QPainter, QPalette, QPolygon, QRasterWindow
+from PySide6.QtGui import (QGuiApplication, QPainter, QPainterStateGuard,
+ QPalette, QPolygon, QRasterWindow)
"""Simplified PySide6 port of the gui/analogclock example from Qt v6.x"""
painter.setPen(Qt.PenStyle.NoPen)
painter.setBrush(self._hour_color)
- painter.save()
- painter.rotate(30.0 * ((time.hour() + time.minute() / 60.0)))
- painter.drawConvexPolygon(self._hour_hand)
- painter.restore()
+ with QPainterStateGuard(painter):
+ painter.rotate(30.0 * ((time.hour() + time.minute() / 60.0)))
+ painter.drawConvexPolygon(self._hour_hand)
for _ in range(0, 12):
painter.drawRect(73, -3, 16, 6)
painter.setBrush(self._minute_color)
- painter.save()
- painter.rotate(6.0 * time.minute())
- painter.drawConvexPolygon(self._minute_hand)
- painter.restore()
+ with QPainterStateGuard(painter):
+ painter.rotate(6.0 * time.minute())
+ painter.drawConvexPolygon(self._minute_hand)
painter.setBrush(self._seconds_color)
- painter.save()
- painter.rotate(6.0 * time.second())
- painter.drawConvexPolygon(self._seconds_hand)
- painter.drawEllipse(-3, -3, 6, 6)
- painter.drawEllipse(-5, -68, 10, 10)
- painter.restore()
+ with QPainterStateGuard(painter):
+ painter.rotate(6.0 * time.second())
+ painter.drawConvexPolygon(self._seconds_hand)
+ painter.drawEllipse(-3, -3, 6, 6)
+ painter.drawEllipse(-5, -68, 10, 10)
painter.setPen(self._minute_color)
@Slot(QAction)
def updateCameraDevice(self, action):
- self.setCamera(QCameraDevice(action))
+ self.setCamera(QCameraDevice(action.data()))
@Slot()
def displayViewfinder(self):
QtObject {
id: root
- property QtObject defaultProfile: WebEngineProfile {
+ property QtObject defaultProfilePrototype : WebEngineProfilePrototype {
storageName: "Profile"
- offTheRecord: false
+ Component.onCompleted: {
+ let fullVersionList = defaultProfilePrototype.instance().clientHints.fullVersionList;
+ fullVersionList["QuickNanoBrowser"] = "1.0";
+ defaultProfilePrototype.instance().clientHints.fullVersionList = fullVersionList;
+ }
}
- property QtObject otrProfile: WebEngineProfile {
- offTheRecord: true
+ property QtObject otrPrototype : WebEngineProfilePrototype {
}
property Component browserWindowComponent: BrowserWindow {
return newDialog;
}
function load(url) {
- var browserWindow = createWindow(defaultProfile);
+ var browserWindow = createWindow(defaultProfilePrototype.instance());
browserWindow.currentWebView.url = url;
}
}
import QtCore
import QtQml
import QtQuick
-import QtQuick.Controls
+import QtQuick.Controls.Fusion
+import QtQuick.Dialogs
import QtQuick.Layouts
import QtQuick.Window
import QtWebEngine
property alias webRTCPublicInterfacesOnly : webRTCPublicInterfacesOnly.checked
property alias devToolsEnabled: devToolsEnabled.checked
property alias pdfViewerEnabled: pdfViewerEnabled.checked
+ property int imageAnimationPolicy: WebEngineSettings.ImageAnimationPolicy.Allow
}
Action {
Action {
shortcut: StandardKey.AddTab
onTriggered: {
- tabBar.createTab(tabBar.count != 0 ? currentWebView.profile : defaultProfile);
+ tabBar.createTab(tabBar.count != 0 ? currentWebView.profile : defaultProfilePrototype.instance());
addressBar.forceActiveFocus();
addressBar.selectAll();
}
id: offTheRecordEnabled
text: "Off The Record"
checkable: true
- checked: currentWebView && currentWebView.profile === otrProfile
+ checked: currentWebView && currentWebView.profile === otrPrototype.instance()
onToggled: function(checked) {
if (currentWebView) {
- currentWebView.profile = checked ? otrProfile : defaultProfile;
+ currentWebView.profile = checked ? otrPrototype.instance() : defaultProfilePrototype.instance();
}
}
}
}
MenuItem {
id: pdfViewerEnabled
- text: "PDF viewer enabled"
+ text: "PDF Viewer Enabled"
checkable: true
checked: WebEngine.settings.pdfViewerEnabled
}
+
+ Menu {
+ id: imageAnimationPolicy
+ title: "Image Animation Policy"
+
+ MenuItem {
+ id: disableImageAnimation
+ text: "Disable All Image Animation"
+ checkable: true
+ autoExclusive: true
+ checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.ImageAnimationPolicy.Disallow
+ onTriggered: {
+ appSettings.imageAnimationPolicy = WebEngineSettings.ImageAnimationPolicy.Disallow
+ }
+ }
+
+ MenuItem {
+ id: allowImageAnimation
+ text: "Allow All Animated Images"
+ checkable: true
+ autoExclusive: true
+ checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.ImageAnimationPolicy.Allow
+ onTriggered : {
+ appSettings.imageAnimationPolicy = WebEngineSettings.ImageAnimationPolicy.Allow
+ }
+ }
+
+ MenuItem {
+ id: animateImageOnce
+ text: "Animate Image Once"
+ checkable: true
+ autoExclusive: true
+ checked: WebEngine.settings.imageAnimationPolicy === WebEngineSettings.ImageAnimationPolicy.AnimateOnce
+ onTriggered : {
+ appSettings.imageAnimationPolicy = WebEngineSettings.ImageAnimationPolicy.AnimateOnce
+ }
+ }
+ }
+
}
}
}
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
- Component.onCompleted: createTab(defaultProfile)
+ Component.onCompleted: createTab(defaultProfilePrototype.instance())
function createTab(profile, focusOnNewTab = true, url = undefined) {
var webview = tabComponent.createObject(tabLayout, {profile: profile});
}
function removeView(index) {
- tabBar.removeItem(index);
if (tabBar.count > 1) {
tabBar.removeItem(tabBar.itemAt(index));
tabLayout.children[index].destroy();
settings.touchIconsEnabled: appSettings.touchIconsEnabled
settings.webRTCPublicInterfacesOnly: appSettings.webRTCPublicInterfacesOnly
settings.pdfViewerEnabled: appSettings.pdfViewerEnabled
+ settings.imageAnimationPolicy: appSettings.imageAnimationPolicy
+ settings.screenCaptureEnabled: true
onCertificateError: function(error) {
+ if (!error.isMainFrame) {
+ error.rejectCertificate();
+ return;
+ }
+
error.defer();
sslDialog.enqueue(error);
}
request.accept();
}
+ onDesktopMediaRequested: function(request) {
+ // select the primary screen
+ request.selectScreen(request.screensModel.index(0, 0));
+ }
+
onRenderProcessTerminated: function(terminationStatus, exitCode) {
var status = "";
switch (terminationStatus) {
findBar.reset();
}
- onFeaturePermissionRequested: function(securityOrigin, feature) {
- featurePermissionDialog.securityOrigin = securityOrigin;
- featurePermissionDialog.feature = feature;
- featurePermissionDialog.visible = true;
+ onPermissionRequested: function(permission) {
+ permissionDialog.permission = permission;
+ permissionDialog.visible = true;
+ }
+ onWebAuthUxRequested: function(request) {
+ webAuthDialog.init(request);
}
Timer {
}
}
Dialog {
- id: featurePermissionDialog
+ id: permissionDialog
anchors.centerIn: parent
width: Math.min(browserWindow.width, browserWindow.height) / 3 * 2
contentWidth: mainTextForPermissionDialog.width
standardButtons: Dialog.No | Dialog.Yes
title: "Permission Request"
- property var feature;
- property url securityOrigin;
+ property var permission;
contentItem: Item {
Label {
id: mainTextForPermissionDialog
- text: featurePermissionDialog.questionForFeature()
}
}
- onAccepted: currentWebView && currentWebView.grantFeaturePermission(securityOrigin, feature, true)
- onRejected: currentWebView && currentWebView.grantFeaturePermission(securityOrigin, feature, false)
+ onAccepted: permission.grant()
+ onRejected: permission.deny()
onVisibleChanged: {
- if (visible)
+ if (visible) {
+ mainTextForPermissionDialog.text = questionForPermissionType();
width = contentWidth + 20;
+ }
}
- function questionForFeature() {
- var question = "Allow " + securityOrigin + " to "
+ function questionForPermissionType() {
+ var question = "Allow " + permission.origin + " to "
- switch (feature) {
- case WebEngineView.Geolocation:
+ switch (permission.permissionType) {
+ case WebEnginePermission.PermissionType.Geolocation:
question += "access your location information?";
break;
- case WebEngineView.MediaAudioCapture:
+ case WebEnginePermission.PermissionType.MediaAudioCapture:
question += "access your microphone?";
break;
- case WebEngineView.MediaVideoCapture:
+ case WebEnginePermission.PermissionType.MediaVideoCapture:
question += "access your webcam?";
break;
- case WebEngineView.MediaVideoCapture:
+ case WebEnginePermission.PermissionType.MediaAudioVideoCapture:
question += "access your microphone and webcam?";
break;
- case WebEngineView.MouseLock:
+ case WebEnginePermission.PermissionType.MouseLock:
question += "lock your mouse cursor?";
break;
- case WebEngineView.DesktopVideoCapture:
+ case WebEnginePermission.PermissionType.DesktopVideoCapture:
question += "capture video of your desktop?";
break;
- case WebEngineView.DesktopAudioVideoCapture:
+ case WebEnginePermission.PermissionType.DesktopAudioVideoCapture:
question += "capture audio and video of your desktop?";
break;
- case WebEngineView.Notifications:
+ case WebEnginePermission.PermissionType.Notifications:
question += "show notification on your desktop?";
break;
+ case WebEnginePermission.PermissionType.ClipboardReadWrite:
+ question += "read from and write to your clipboard?";
+ break;
+ case WebEnginePermission.PermissionType.LocalFontsAccess:
+ question += "access the fonts stored on your machine?";
+ break;
default:
- question += "access unknown or unsupported feature [" + feature + "] ?";
+ question += "access unknown or unsupported permission type [" + permission.permissionType + "] ?";
break;
}
anchors.fill: parent
}
+ WebAuthDialog {
+ id: webAuthDialog
+ visible: false
+ }
+
+ MessageDialog {
+ id: downloadAcceptDialog
+ property var downloadRequest: downloadView.pendingDownloadRequest
+ title: "Download requested"
+ text: downloadRequest ? downloadRequest.suggestedFileName : ""
+ buttons: Dialog.No | Dialog.Yes
+ onAccepted: {
+ downloadView.visible = true;
+ downloadView.append(downloadRequest);
+ downloadRequest.accept();
+ }
+ onRejected: {
+ downloadRequest.cancel();
+ }
+ onButtonClicked: {
+ visible = false;
+ }
+ visible: false
+ }
+
function onDownloadRequested(download) {
- downloadView.visible = true;
- downloadView.append(download);
- download.accept();
+ downloadView.pendingDownloadRequest = download;
+ downloadAcceptDialog.visible = true;
}
FindBar {
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
-import QtQuick.Controls
+import QtQuick.Controls.Fusion
import QtWebEngine
import QtQuick.Layouts
Rectangle {
id: downloadView
color: "lightgray"
+ property var pendingDownloadRequest: null
ListModel {
id: downloadModel
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
-import QtQuick.Controls
+import QtQuick.Controls.Fusion
import QtQuick.Layouts
Rectangle {
TextField {
id: findTextField
anchors.fill: parent
+ color: "black"
background: Rectangle {
color: "transparent"
}
Label {
text: activeMatch + "/" + numberOfMatches
visible: findTextField.text != ""
+ color: "black"
}
Rectangle {
text: "<"
enabled: numberOfMatches > 0
onClicked: root.findPrevious()
+ contentItem: Text {
+ color: "black"
+ text: parent.text
+ }
}
ToolButton {
text: ">"
enabled: numberOfMatches > 0
onClicked: root.findNext()
+ contentItem: Text {
+ color: "black"
+ text: parent.text
+ }
}
ToolButton {
text: "x"
onClicked: root.visible = false
+ contentItem: Text {
+ color: "black"
+ text: parent.text
+ }
}
}
}
nargs='?', type=str)
options = argument_parser.parse_args()
- url = url_from_user_input(options.url) if options.url else QUrl("https://www.qt.io")
+ url = url_from_user_input(options.url) if options.url else QUrl("chrome://qt")
app_args = sys.argv
if options.single_process:
# remaining window
self._download_manager_widget.setAttribute(Qt.WidgetAttribute.WA_QuitOnClose, False)
- dp = QWebEngineProfile.defaultProfile()
- dp.downloadRequested.connect(self._download_manager_widget.download_requested)
-
def create_hidden_window(self, offTheRecord=False):
if not offTheRecord and not self._profile:
name = "simplebrowser." + qWebEngineChromiumVersion()
s.setAttribute(QWebEngineSettings.WebAttribute.DnsPrefetchEnabled, True)
s.setAttribute(QWebEngineSettings.WebAttribute.LocalContentCanAccessRemoteUrls, True)
s.setAttribute(QWebEngineSettings.WebAttribute.LocalContentCanAccessFileUrls, False)
+ s.setAttribute(QWebEngineSettings.ScreenCaptureEnabled, True)
self._profile.downloadRequested.connect(
self._download_manager_widget.download_requested)
profile = QWebEngineProfile.defaultProfile() if offTheRecord else self._profile
main_window = BrowserWindow(self, profile, False)
+ profile.setPersistentPermissionsPolicy(
+ QWebEngineProfile.PersistentPermissionsPolicy.AskEveryTime)
+
self._windows.append(main_window)
main_window.about_to_close.connect(self._remove_window)
return main_window
import data.rc_simplebrowser # noqa: F401
+
if __name__ == "__main__":
parser = ArgumentParser(description="Qt Widgets Web Browser",
formatter_class=RawTextHelpFormatter)
QLoggingCategory.setFilterRules("qt.webenginecontext.debug=true")
s = QWebEngineProfile.defaultProfile().settings()
- s.setAttribute(QWebEngineSettings.WebAttribute.PluginsEnabled, True)
- s.setAttribute(QWebEngineSettings.WebAttribute.DnsPrefetchEnabled, True)
+ s.setAttribute(QWebEngineSettings.PluginsEnabled, True)
+ s.setAttribute(QWebEngineSettings.DnsPrefetchEnabled, True)
+ s.setAttribute(QWebEngineSettings.ScreenCaptureEnabled, True)
browser = Browser()
window = browser.create_hidden_window()
- url = QUrl.fromUserInput(args.url) if args.url else QUrl("https://www.qt.io")
+ url = QUrl.fromUserInput(args.url) if args.url else QUrl("chrome://qt")
window.tab_widget().set_url(url)
window.show()
+
sys.exit(app.exec())
--- /dev/null
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'webauthdialog.ui'
+##
+## Created by: Qt User Interface Compiler version 6.8.1
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
+ QFont, QFontDatabase, QGradient, QIcon,
+ QImage, QKeySequence, QLinearGradient, QPainter,
+ QPalette, QPixmap, QRadialGradient, QTransform)
+from PySide6.QtWidgets import (QAbstractButton, QApplication, QDialog, QDialogButtonBox,
+ QGroupBox, QLabel, QLayout, QLineEdit,
+ QSizePolicy, QVBoxLayout, QWidget)
+
+class Ui_WebAuthDialog(object):
+ def setupUi(self, WebAuthDialog):
+ if not WebAuthDialog.objectName():
+ WebAuthDialog.setObjectName(u"WebAuthDialog")
+ WebAuthDialog.resize(563, 397)
+ self.buttonBox = QDialogButtonBox(WebAuthDialog)
+ self.buttonBox.setObjectName(u"buttonBox")
+ self.buttonBox.setGeometry(QRect(20, 320, 471, 32))
+ self.buttonBox.setOrientation(Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok|QDialogButtonBox.Retry)
+ self.m_headingLabel = QLabel(WebAuthDialog)
+ self.m_headingLabel.setObjectName(u"m_headingLabel")
+ self.m_headingLabel.setGeometry(QRect(30, 20, 321, 16))
+ self.m_headingLabel.setWordWrap(False)
+ self.m_description = QLabel(WebAuthDialog)
+ self.m_description.setObjectName(u"m_description")
+ self.m_description.setGeometry(QRect(30, 60, 491, 31))
+ self.m_description.setWordWrap(False)
+ self.layoutWidget = QWidget(WebAuthDialog)
+ self.layoutWidget.setObjectName(u"layoutWidget")
+ self.layoutWidget.setGeometry(QRect(20, 100, 471, 171))
+ self.m_mainVerticalLayout = QVBoxLayout(self.layoutWidget)
+ self.m_mainVerticalLayout.setObjectName(u"m_mainVerticalLayout")
+ self.m_mainVerticalLayout.setSizeConstraint(QLayout.SetDefaultConstraint)
+ self.m_mainVerticalLayout.setContentsMargins(0, 0, 0, 0)
+ self.m_pinGroupBox = QGroupBox(self.layoutWidget)
+ self.m_pinGroupBox.setObjectName(u"m_pinGroupBox")
+ self.m_pinGroupBox.setFlat(True)
+ self.m_pinLabel = QLabel(self.m_pinGroupBox)
+ self.m_pinLabel.setObjectName(u"m_pinLabel")
+ self.m_pinLabel.setGeometry(QRect(10, 20, 58, 16))
+ self.m_pinLineEdit = QLineEdit(self.m_pinGroupBox)
+ self.m_pinLineEdit.setObjectName(u"m_pinLineEdit")
+ self.m_pinLineEdit.setGeometry(QRect(90, 20, 113, 21))
+ self.m_confirmPinLabel = QLabel(self.m_pinGroupBox)
+ self.m_confirmPinLabel.setObjectName(u"m_confirmPinLabel")
+ self.m_confirmPinLabel.setGeometry(QRect(10, 50, 81, 16))
+ self.m_confirmPinLineEdit = QLineEdit(self.m_pinGroupBox)
+ self.m_confirmPinLineEdit.setObjectName(u"m_confirmPinLineEdit")
+ self.m_confirmPinLineEdit.setGeometry(QRect(90, 50, 113, 21))
+ self.m_pinEntryErrorLabel = QLabel(self.m_pinGroupBox)
+ self.m_pinEntryErrorLabel.setObjectName(u"m_pinEntryErrorLabel")
+ self.m_pinEntryErrorLabel.setGeometry(QRect(10, 80, 441, 16))
+
+ self.m_mainVerticalLayout.addWidget(self.m_pinGroupBox)
+
+
+ self.retranslateUi(WebAuthDialog)
+
+ QMetaObject.connectSlotsByName(WebAuthDialog)
+ # setupUi
+
+ def retranslateUi(self, WebAuthDialog):
+ WebAuthDialog.setWindowTitle(QCoreApplication.translate("WebAuthDialog", u"Dialog", None))
+ self.m_headingLabel.setText(QCoreApplication.translate("WebAuthDialog", u"Heading", None))
+ self.m_description.setText(QCoreApplication.translate("WebAuthDialog", u"Description", None))
+ self.m_pinGroupBox.setTitle("")
+ self.m_pinLabel.setText(QCoreApplication.translate("WebAuthDialog", u"PIN", None))
+ self.m_confirmPinLabel.setText(QCoreApplication.translate("WebAuthDialog", u"Confirm PIN", None))
+ self.m_pinEntryErrorLabel.setText(QCoreApplication.translate("WebAuthDialog", u"TextLabel", None))
+ # retranslateUi
+
--- /dev/null
+# Converted from webauthdialog.cpp
+
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+from ui_webauthdialog import Ui_WebAuthDialog
+
+from PySide6.QtWidgets import (QDialog, QVBoxLayout, QButtonGroup,
+ QScrollArea, QWidget, QDialogButtonBox,
+ QSizePolicy, QRadioButton)
+from PySide6.QtCore import Qt
+from PySide6.QtWebEngineCore import QWebEngineWebAuthUxRequest
+
+
+class WebAuthDialog(QDialog):
+
+ def __init__(self, request, parent=None):
+ super().__init__(parent)
+
+ self.uxRequest = request
+ self.uiWebAuthDialog = Ui_WebAuthDialog()
+ self.uiWebAuthDialog.setupUi(self)
+
+ self.button_group = QButtonGroup(self)
+ self.button_group.setExclusive(True)
+
+ self.scroll_area = QScrollArea(self)
+ self.select_account_widget = QWidget(self)
+ self.scroll_area.setWidget(self.select_account_widget)
+ self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+ self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
+ self.select_account_widget.resize(400, 150)
+
+ self.select_account_layout = QVBoxLayout(self.select_account_widget)
+ self.uiWebAuthDialog.m_mainVerticalLayout.addWidget(self.scroll_area)
+ self.select_account_layout.setAlignment(Qt.AlignTop)
+
+ self.update_display()
+
+ self.uiWebAuthDialog.buttonBox.rejected.connect(self.onCancelRequest)
+ self.uiWebAuthDialog.buttonBox.accepted.connect(self.onAcceptRequest)
+
+ button = self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Retry)
+ button.clicked.connect(self.onRetry)
+ self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
+
+ def __del__(self):
+ for button in self.button_group.buttons():
+ button.deleteLater()
+
+ if self.button_group:
+ self.button_group.deleteLater()
+ self.button_group = None
+
+ if self.uiWebAuthDialog:
+ del self.uiWebAuthDialog
+ self.uiWebAuthDialog = None
+
+ if self.scroll_area:
+ self.scroll_area.deleteLater()
+ self.scroll_area = None
+
+ def update_display(self):
+ state = self.uxRequest.state()
+ if state == QWebEngineWebAuthUxRequest.WebAuthUxState.SelectAccount:
+ self.setupSelectAccountUI()
+ elif state == QWebEngineWebAuthUxRequest.WebAuthUxState.CollectPin:
+ self.setupCollectPinUI()
+ elif state == QWebEngineWebAuthUxRequest.WebAuthUxState.FinishTokenCollection:
+ self.setupFinishCollectTokenUI()
+ elif state == QWebEngineWebAuthUxRequest.WebAuthUxState.RequestFailed:
+ self.setupErrorUI()
+
+ self.adjustSize()
+
+ def setupSelectAccountUI(self):
+ self.uiWebAuthDialog.m_headingLabel.setText(self.tr("Choose a Passkey"))
+ self.uiWebAuthDialog.m_description.setText(self.tr("Which passkey do you want to use for ")
+ + self.uxRequest.relyingPartyId()
+ + self.tr("? "))
+ self.uiWebAuthDialog.m_pinGroupBox.setVisible(False)
+ self.uiWebAuthDialog.m_mainVerticalLayout.removeWidget(self.uiWebAuthDialog.m_pinGroupBox)
+ self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Retry).setVisible(False)
+
+ self.clearSelectAccountButtons()
+ self.scroll_area.setVisible(True)
+ self.select_account_widget.resize(self.width(), self.height())
+ userNames = self.uxRequest.userNames()
+ # Create radio buttons for each name
+ for name in userNames:
+ radioButton = QRadioButton(name)
+ self.select_account_layout.addWidget(radioButton)
+ self.button_group.addButton(radioButton)
+
+ self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("Ok"))
+ self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Ok).setVisible(True)
+ self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Cancel).setVisible(True)
+ self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Retry).setVisible(False)
+
+ def setupFinishCollectTokenUI(self):
+
+ self.clearSelectAccountButtons()
+ self.uiWebAuthDialog.m_headingLabel.setText(self.tr("Use your security key with")
+ + self.uxRequest.relyingPartyId())
+ self.uiWebAuthDialog.m_description.setText(
+ self.tr("Touch your security key again to complete the request."))
+ self.uiWebAuthDialog.m_pinGroupBox.setVisible(False)
+ self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Ok).setVisible(False)
+ self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Retry).setVisible(False)
+ self.scroll_area.setVisible(False)
+
+ def setupCollectPinUI(self):
+
+ self.clearSelectAccountButtons()
+ self.uiWebAuthDialog.m_mainVerticalLayout.addWidget(self.uiWebAuthDialog.m_pinGroupBox)
+ self.uiWebAuthDialog.m_pinGroupBox.setVisible(True)
+ self.uiWebAuthDialog.m_confirmPinLabel.setVisible(False)
+ self.uiWebAuthDialog.m_confirmPinLineEdit.setVisible(False)
+ self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("Next"))
+ self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Ok).setVisible(True)
+ self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Cancel).setVisible(True)
+ self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Retry).setVisible(False)
+ self.scroll_area.setVisible(False)
+
+ pinRequestInfo = self.uxRequest.pinRequest()
+
+ if pinRequestInfo.reason == QWebEngineWebAuthUxRequest.PinEntryReason.Challenge:
+ self.uiWebAuthDialog.m_headingLabel.setText(self.tr("PIN Required"))
+ self.uiWebAuthDialog.m_description.setText(
+ self.tr("Enter the PIN for your security key"))
+ self.uiWebAuthDialog.m_confirmPinLabel.setVisible(False)
+ self.uiWebAuthDialog.m_confirmPinLineEdit.setVisible(False)
+ else:
+ if pinRequestInfo.reason == QWebEngineWebAuthUxRequest.PinEntryReason.Set:
+ self.uiWebAuthDialog.m_headingLabel.setText(self.tr("New PIN Required"))
+ self.uiWebAuthDialog.m_description.setText(
+ self.tr("Set new PIN for your security key"))
+ else:
+ self.uiWebAuthDialog.m_headingLabel.setText(self.tr("Change PIN Required"))
+ self.uiWebAuthDialog.m_description.setText(
+ self.tr("Change PIN for your security key"))
+
+ self.uiWebAuthDialog.m_confirmPinLabel.setVisible(True)
+ self.uiWebAuthDialog.m_confirmPinLineEdit.setVisible(True)
+
+ errorDetails = ""
+
+ if pinRequestInfo.error == QWebEngineWebAuthUxRequest.PinEntryError.InternalUvLocked:
+ errorDetails = self.tr("Internal User Verification Locked ")
+ elif pinRequestInfo.error == QWebEngineWebAuthUxRequest.PinEntryError.WrongPin:
+ errorDetails = self.tr("Wrong PIN")
+ elif pinRequestInfo.error == QWebEngineWebAuthUxRequest.PinEntryError.TooShort:
+ errorDetails = self.tr("Too Short")
+ elif pinRequestInfo.error == QWebEngineWebAuthUxRequest.PinEntryError.InvalidCharacters:
+ errorDetails = self.tr("Invalid Characters")
+ elif pinRequestInfo.error == QWebEngineWebAuthUxRequest.PinEntryError.SameAsCurrentPin:
+ errorDetails = self.tr("Same as current PIN")
+
+ if errorDetails:
+ errorDetails += f" {pinRequestInfo.remainingAttempts} attempts remaining"
+
+ self.uiWebAuthDialog.m_pinEntryErrorLabel.setText(errorDetails)
+
+ def onCancelRequest(self):
+
+ self.uxRequest.cancel()
+
+ def onAcceptRequest(self):
+
+ state = self.uxRequest.state()
+ if state == QWebEngineWebAuthUxRequest.WebAuthUxState.SelectAccount:
+ if self.button_group.checkedButton():
+ self.uxRequest.setSelectedAccount(self.button_group.checkedButton().text())
+ elif state == QWebEngineWebAuthUxRequest.WebAuthUxState.CollectPin:
+ self.uxRequest.setPin(self.uiWebAuthDialog.m_pinLineEdit.text())
+
+ def setupErrorUI(self):
+
+ self.clearSelectAccountButtons()
+ error_description = ""
+ error_heading = self.tr("Something went wrong")
+ isVisibleRetry = False
+
+ state = self.uxRequest.requestFailureReason()
+ failure_reason = QWebEngineWebAuthUxRequest.RequestFailureReason
+
+ if state == failure_reason.Timeout:
+ error_description = self.tr("Request Timeout")
+ elif state == failure_reason.KeyNotRegistered:
+ error_description = self.tr("Key not registered")
+ elif state == failure_reason.KeyAlreadyRegistered:
+ error_description = self.tr("You already registered self device."
+ "Try again with device")
+ isVisibleRetry = True
+ elif state == failure_reason.SoftPinBlock:
+ error_description = self.tr(
+ "The security key is locked because the wrong PIN was entered too many times."
+ "To unlock it, remove and reinsert it.")
+ isVisibleRetry = True
+ elif state == failure_reason.HardPinBlock:
+ error_description = self.tr(
+ "The security key is locked because the wrong PIN was entered too many times."
+ " Yo'll need to reset the security key.")
+ elif state == failure_reason.AuthenticatorRemovedDuringPinEntry:
+ error_description = self.tr(
+ "Authenticator removed during verification. Please reinsert and try again")
+ elif state == failure_reason.AuthenticatorMissingResidentKeys:
+ error_description = self.tr("Authenticator doesn't have resident key support")
+ elif state == failure_reason.AuthenticatorMissingUserVerification:
+ error_description = self.tr("Authenticator missing user verification")
+ elif state == failure_reason.AuthenticatorMissingLargeBlob:
+ error_description = self.tr("Authenticator missing Large Blob support")
+ elif state == failure_reason.NoCommonAlgorithms:
+ error_description = self.tr("Authenticator missing Large Blob support")
+ elif state == failure_reason.StorageFull:
+ error_description = self.tr("Storage Full")
+ elif state == failure_reason.UserConsentDenied:
+ error_description = self.tr("User consent denied")
+ elif state == failure_reason.WinUserCancelled:
+ error_description = self.tr("User Cancelled Request")
+
+ self.uiWebAuthDialog.m_headingLabel.setText(error_heading)
+ self.uiWebAuthDialog.m_description.setText(error_description)
+ self.uiWebAuthDialog.m_description.adjustSize()
+ self.uiWebAuthDialog.m_pinGroupBox.setVisible(False)
+ self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Ok).setVisible(False)
+ self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Retry).setVisible(isVisibleRetry)
+ if isVisibleRetry:
+ self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Retry).setFocus()
+ self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Cancel).setVisible(True)
+ self.uiWebAuthDialog.buttonBox.button(QDialogButtonBox.Cancel).setText(self.tr("Close"))
+ self.scroll_area.setVisible(False)
+
+ def onRetry(self):
+ self.uxRequest.retry()
+
+ def clearSelectAccountButtons(self):
+ buttons = self.button_group.buttons()
+
+ for radio_button in buttons:
+ self.select_account_layout.removeWidget(radio_button)
+ self.button_group.removeButton(radio_button)
+ radio_button.deleteLater()
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>WebAuthDialog</class>
+ <widget class="QDialog" name="WebAuthDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>563</width>
+ <height>397</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="geometry">
+ <rect>
+ <x>20</x>
+ <y>320</y>
+ <width>471</width>
+ <height>32</height>
+ </rect>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Retry</set>
+ </property>
+ </widget>
+ <widget class="QLabel" name="m_headingLabel">
+ <property name="geometry">
+ <rect>
+ <x>30</x>
+ <y>20</y>
+ <width>321</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Heading</string>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ <widget class="QLabel" name="m_description">
+ <property name="geometry">
+ <rect>
+ <x>30</x>
+ <y>60</y>
+ <width>491</width>
+ <height>31</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Description</string>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ <widget class="QWidget" name="layoutWidget">
+ <property name="geometry">
+ <rect>
+ <x>20</x>
+ <y>100</y>
+ <width>471</width>
+ <height>171</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="m_mainVerticalLayout">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetDefaultConstraint</enum>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="m_pinGroupBox">
+ <property name="title">
+ <string/>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ <widget class="QLabel" name="m_pinLabel">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>20</y>
+ <width>58</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>PIN</string>
+ </property>
+ </widget>
+ <widget class="QLineEdit" name="m_pinLineEdit">
+ <property name="geometry">
+ <rect>
+ <x>90</x>
+ <y>20</y>
+ <width>113</width>
+ <height>21</height>
+ </rect>
+ </property>
+ </widget>
+ <widget class="QLabel" name="m_confirmPinLabel">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>50</y>
+ <width>81</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Confirm PIN</string>
+ </property>
+ </widget>
+ <widget class="QLineEdit" name="m_confirmPinLineEdit">
+ <property name="geometry">
+ <rect>
+ <x>90</x>
+ <y>50</y>
+ <width>113</width>
+ <height>21</height>
+ </rect>
+ </property>
+ </widget>
+ <widget class="QLabel" name="m_pinEntryErrorLabel">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>80</y>
+ <width>441</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>TextLabel</string>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
def __init__(self, view, profile, parent=None):
super().__init__(parent, Qt.Window)
- self.m_urlLineEdit = QLineEdit(self)
self._url_line_edit = QLineEdit()
self._fav_action = QAction(self)
self._view = view
from functools import partial
from PySide6.QtWebEngineCore import (QWebEngineFileSystemAccessRequest,
- QWebEnginePage)
+ QWebEnginePage,
+ QWebEngineWebAuthUxRequest)
from PySide6.QtWebEngineWidgets import QWebEngineView
from PySide6.QtWidgets import QDialog, QMessageBox, QStyle
from PySide6.QtGui import QIcon
from PySide6.QtNetwork import QAuthenticator
-from PySide6.QtCore import QTimer, Signal, Slot
+from PySide6.QtCore import QTimer, Signal, Slot, Qt
from webpage import WebPage
from webpopupwindow import WebPopupWindow
from ui_passworddialog import Ui_PasswordDialog
from ui_certificateerrordialog import Ui_CertificateErrorDialog
+from webauthdialog import WebAuthDialog
def question_for_feature(feature):
+
if feature == QWebEnginePage.Geolocation:
return "Allow %1 to access your location information?"
if feature == QWebEnginePage.MediaAudioCapture:
self._loading_icon = QIcon.fromTheme(QIcon.ThemeIcon.ViewRefresh,
QIcon(":view-refresh.png"))
self._default_icon = QIcon(":text-html.png")
+ self.auth_dialog = None
@Slot()
def _load_started(self):
self.handle_proxy_authentication_required)
old_page.registerProtocolHandlerRequested.disconnect(
self.handle_register_protocol_handler_requested)
+ old_page.webAuthUxRequested.disconnect(self.handle_web_auth_ux_requested)
old_page.fileSystemAccessRequested.disconnect(self.handle_file_system_access_requested)
self.create_web_action_trigger(page, QWebEnginePage.WebAction.Forward)
page.proxyAuthenticationRequired.connect(self.handle_proxy_authentication_required)
page.registerProtocolHandlerRequested.connect(
self.handle_register_protocol_handler_requested)
+ page.webAuthUxRequested.connect(self.handle_web_auth_ux_requested)
page.fileSystemAccessRequested.connect(self.handle_file_system_access_requested)
def load_progress(self):
# Set authenticator null if dialog is cancelled
auth = QAuthenticator()
+ def handle_web_auth_ux_requested(self, request):
+ if self.auth_dialog:
+ self.auth_dialog.deleteLater()
+
+ self.auth_dialog = WebAuthDialog(request, self.window())
+ self.auth_dialog.setModal(False)
+ self.auth_dialog.setWindowFlags(self.auth_dialog.windowFlags()
+ & ~Qt.WindowContextHelpButtonHint)
+
+ request.stateChanged.connect(self.on_state_changed)
+ self.auth_dialog.show()
+
+ def on_state_changed(self, state):
+ if state in (QWebEngineWebAuthUxRequest.WebAuthUxState.Completed,
+ QWebEngineWebAuthUxRequest.WebAuthUxState.Cancelled):
+ if self.auth_dialog:
+ self.auth_dialog.deleteLater()
+ self.auth_dialog = None
+ else:
+ if self.auth_dialog:
+ self.auth_dialog.update_display()
+
def handle_register_protocol_handler_requested(self, request):
host = request.origin().host()
m = f"Allow {host} to open all {request.scheme()} links?"
from math import (cos, sin, pi)
-from PySide6.QtGui import (QPainter, QPolygonF)
+from PySide6.QtGui import (QPainter, QPainterStateGuard, QPolygonF)
from PySide6.QtCore import (QPointF, QSize, Qt)
PAINTING_SCALE_FACTOR = 20
def paint(self, painter, rect, palette, isEditable=False):
""" Paint the stars (and/or diamonds if we're in editing mode). """
- painter.save()
-
- painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
- painter.setPen(Qt.PenStyle.NoPen)
-
- if isEditable:
- painter.setBrush(palette.highlight())
- else:
- painter.setBrush(palette.windowText())
-
- y_offset = (rect.height() - PAINTING_SCALE_FACTOR) / 2
- painter.translate(rect.x(), rect.y() + y_offset)
- painter.scale(PAINTING_SCALE_FACTOR, PAINTING_SCALE_FACTOR)
-
- for i in range(self.MAX_STAR_COUNT):
- if i < self.star_count:
- painter.drawPolygon(self._star_polygon, Qt.FillRule.WindingFill)
- elif isEditable:
- painter.drawPolygon(self._diamond_polygon, Qt.WindingFill)
- painter.translate(1.0, 0.0)
-
- painter.restore()
+ with QPainterStateGuard(painter):
+ painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
+ painter.setPen(Qt.NoPen)
+
+ if isEditable:
+ painter.setBrush(palette.highlight())
+ else:
+ painter.setBrush(palette.windowText())
+
+ y_offset = (rect.height() - PAINTING_SCALE_FACTOR) / 2
+ painter.translate(rect.x(), rect.y() + y_offset)
+ painter.scale(PAINTING_SCALE_FACTOR, PAINTING_SCALE_FACTOR)
+
+ for i in range(self.MAX_STAR_COUNT):
+ if i < self.star_count:
+ painter.drawPolygon(self._star_polygon, Qt.FillRule.WindingFill)
+ elif isEditable:
+ painter.drawPolygon(self._diamond_polygon, Qt.FillRule.WindingFill)
+ painter.translate(1.0, 0.0)
from PySide6.QtCore import QPoint, QRect, QSize, Qt, qVersion
from PySide6.QtGui import (QBrush, QConicalGradient, QLinearGradient, QPainter,
- QPainterPath, QPalette, QPen, QPixmap, QPolygon,
- QRadialGradient)
+ QPainterStateGuard, QPainterPath, QPalette, QPen,
+ QPixmap, QPolygon, QRadialGradient)
from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QGridLayout,
QLabel, QSpinBox, QWidget)
for x in range(0, self.width(), 100):
for y in range(0, self.height(), 100):
- painter.save()
- painter.translate(x, y)
- if self.transformed:
- painter.translate(50, 50)
- painter.rotate(60.0)
- painter.scale(0.6, 0.9)
- painter.translate(-50, -50)
-
- if self.shape == RenderArea.Line:
- painter.drawLine(rect.bottomLeft(), rect.topRight())
- elif self.shape == RenderArea.Points:
- painter.drawPoints(RenderArea.points)
- elif self.shape == RenderArea.Polyline:
- painter.drawPolyline(RenderArea.points)
- elif self.shape == RenderArea.Polygon:
- painter.drawPolygon(RenderArea.points)
- elif self.shape == RenderArea.Rect:
- painter.drawRect(rect)
- elif self.shape == RenderArea.RoundedRect:
- painter.drawRoundedRect(rect, 25, 25, Qt.SizeMode.RelativeSize)
- elif self.shape == RenderArea.Ellipse:
- painter.drawEllipse(rect)
- elif self.shape == RenderArea.Arc:
- painter.drawArc(rect, start_angle, arc_length)
- elif self.shape == RenderArea.Chord:
- painter.drawChord(rect, start_angle, arc_length)
- elif self.shape == RenderArea.Pie:
- painter.drawPie(rect, start_angle, arc_length)
- elif self.shape == RenderArea.Path:
- painter.drawPath(path)
- elif self.shape == RenderArea.Text:
- qv = qVersion()
- painter.drawText(rect, Qt.AlignmentFlag.AlignCenter,
- f"PySide 6\nQt {qv}")
- elif self.shape == RenderArea.Pixmap:
- painter.drawPixmap(10, 10, self.pixmap)
-
- painter.restore()
+ with QPainterStateGuard(painter):
+ painter.translate(x, y)
+ if self.transformed:
+ painter.translate(50, 50)
+ painter.rotate(60.0)
+ painter.scale(0.6, 0.9)
+ painter.translate(-50, -50)
+
+ if self.shape == RenderArea.Line:
+ painter.drawLine(rect.bottomLeft(), rect.topRight())
+ elif self.shape == RenderArea.Points:
+ painter.drawPoints(RenderArea.points)
+ elif self.shape == RenderArea.Polyline:
+ painter.drawPolyline(RenderArea.points)
+ elif self.shape == RenderArea.Polygon:
+ painter.drawPolygon(RenderArea.points)
+ elif self.shape == RenderArea.Rect:
+ painter.drawRect(rect)
+ elif self.shape == RenderArea.RoundedRect:
+ painter.drawRoundedRect(rect, 25, 25, Qt.SizeMode.RelativeSize)
+ elif self.shape == RenderArea.Ellipse:
+ painter.drawEllipse(rect)
+ elif self.shape == RenderArea.Arc:
+ painter.drawArc(rect, start_angle, arc_length)
+ elif self.shape == RenderArea.Chord:
+ painter.drawChord(rect, start_angle, arc_length)
+ elif self.shape == RenderArea.Pie:
+ painter.drawPie(rect, start_angle, arc_length)
+ elif self.shape == RenderArea.Path:
+ painter.drawPath(path)
+ elif self.shape == RenderArea.Text:
+ qv = qVersion()
+ painter.drawText(rect, Qt.AlignmentFlag.AlignCenter,
+ f"PySide 6\nQt {qv}")
+ elif self.shape == RenderArea.Pixmap:
+ painter.drawPixmap(10, 10, self.pixmap)
painter.setPen(self.palette().dark().color())
painter.setBrush(Qt.BrushStyle.NoBrush)
+++ /dev/null
-{
- "files": ["part3.py", "part1.py", "part5.py", "part2.py",
- "part7.py", "part6.py", "part4.py"]
-}
+++ /dev/null
-Address Book Example
-====================
-
-The address book example shows how to use proxy models to display different
-views onto data from a single model.
-
-.. image:: addressbook.png
- :width: 400
- :alt: Address Book Screenshot
+++ /dev/null
-# Copyright (C) 2013 Riverbank Computing Limited.
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from __future__ import annotations
-
-import sys
-
-from PySide6.QtCore import Qt
-from PySide6.QtWidgets import (QApplication, QGridLayout, QLabel, QLineEdit,
- QTextEdit, QWidget)
-
-
-class AddressBook(QWidget):
- def __init__(self, parent=None):
- super().__init__(parent)
-
- name_label = QLabel("Name:")
- self._name_line = QLineEdit()
-
- address_label = QLabel("Address:")
- self._address_text = QTextEdit()
-
- main_layout = QGridLayout()
- main_layout.addWidget(name_label, 0, 0)
- main_layout.addWidget(self._name_line, 0, 1)
- main_layout.addWidget(address_label, 1, 0, Qt.AlignmentFlag.AlignTop)
- main_layout.addWidget(self._address_text, 1, 1)
-
- self.setLayout(main_layout)
- self.setWindowTitle("Simple Address Book")
-
-
-if __name__ == '__main__':
- app = QApplication(sys.argv)
-
- address_book = AddressBook()
- address_book.show()
-
- sys.exit(app.exec())
+++ /dev/null
-# Copyright (C) 2013 Riverbank Computing Limited.
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from __future__ import annotations
-
-import sys
-
-from PySide6.QtCore import Qt
-from PySide6.QtWidgets import (QApplication, QGridLayout,
- QLabel, QLineEdit,
- QMessageBox, QPushButton, QTextEdit,
- QVBoxLayout, QWidget)
-
-
-class SortedDict(dict):
- class Iterator:
- def __init__(self, sorted_dict):
- self._dict = sorted_dict
- self._keys = sorted(self._dict.keys())
- self._nr_items = len(self._keys)
- self._idx = 0
-
- def __iter__(self):
- return self
-
- def next(self):
- if self._idx >= self._nr_items:
- raise StopIteration
-
- key = self._keys[self._idx]
- value = self._dict[key]
- self._idx += 1
-
- return key, value
-
- __next__ = next
-
- def __iter__(self):
- return SortedDict.Iterator(self)
-
- iterkeys = __iter__
-
-
-class AddressBook(QWidget):
- def __init__(self, parent=None):
- super().__init__(parent)
-
- self.contacts = SortedDict()
- self._old_name = ''
- self._old_address = ''
-
- name_label = QLabel("Name:")
- self._name_line = QLineEdit()
- self._name_line.setReadOnly(True)
-
- address_label = QLabel("Address:")
- self._address_text = QTextEdit()
- self._address_text.setReadOnly(True)
-
- self._add_button = QPushButton("&Add")
- self._submit_button = QPushButton("&Submit")
- self._submit_button.hide()
- self._cancel_button = QPushButton("&Cancel")
- self._cancel_button.hide()
-
- self._add_button.clicked.connect(self.add_contact)
- self._submit_button.clicked.connect(self.submit_contact)
- self._cancel_button.clicked.connect(self.cancel)
-
- button_layout_1 = QVBoxLayout()
- button_layout_1.addWidget(self._add_button, Qt.AlignmentFlag.AlignTop)
- button_layout_1.addWidget(self._submit_button)
- button_layout_1.addWidget(self._cancel_button)
- button_layout_1.addStretch()
-
- main_layout = QGridLayout()
- main_layout.addWidget(name_label, 0, 0)
- main_layout.addWidget(self._name_line, 0, 1)
- main_layout.addWidget(address_label, 1, 0, Qt.AlignmentFlag.AlignTop)
- main_layout.addWidget(self._address_text, 1, 1)
- main_layout.addLayout(button_layout_1, 1, 2)
-
- self.setLayout(main_layout)
- self.setWindowTitle("Simple Address Book")
-
- def add_contact(self):
- self._old_name = self._name_line.text()
- self._old_address = self._address_text.toPlainText()
-
- self._name_line.clear()
- self._address_text.clear()
-
- self._name_line.setReadOnly(False)
- self._name_line.setFocus(Qt.FocusReason.OtherFocusReason)
- self._address_text.setReadOnly(False)
-
- self._add_button.setEnabled(False)
- self._submit_button.show()
- self._cancel_button.show()
-
- def submit_contact(self):
- name = self._name_line.text()
- address = self._address_text.toPlainText()
-
- if name == "" or address == "":
- QMessageBox.information(self, "Empty Field", "Please enter a name and address.")
- return
-
- if name not in self.contacts:
- self.contacts[name] = address
- QMessageBox.information(self, "Add Successful",
- f'"{name}" has been added to your address book.')
- else:
- QMessageBox.information(self, "Add Unsuccessful",
- f'Sorry, "{name}" is already in your address book.')
- return
-
- if not self.contacts:
- self._name_line.clear()
- self._address_text.clear()
-
- self._name_line.setReadOnly(True)
- self._address_text.setReadOnly(True)
- self._add_button.setEnabled(True)
- self._submit_button.hide()
- self._cancel_button.hide()
-
- def cancel(self):
- self._name_line.setText(self._old_name)
- self._name_line.setReadOnly(True)
-
- self._address_text.setText(self._old_address)
- self._address_text.setReadOnly(True)
-
- self._add_button.setEnabled(True)
- self._submit_button.hide()
- self._cancel_button.hide()
-
-
-if __name__ == '__main__':
- app = QApplication(sys.argv)
-
- address_book = AddressBook()
- address_book.show()
-
- sys.exit(app.exec())
+++ /dev/null
-# Copyright (C) 2013 Riverbank Computing Limited.
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from __future__ import annotations
-
-import sys
-
-from PySide6.QtCore import Qt, Slot
-from PySide6.QtWidgets import (QApplication, QGridLayout,
- QHBoxLayout, QLabel, QLineEdit,
- QMessageBox, QPushButton, QTextEdit,
- QVBoxLayout, QWidget)
-
-
-class SortedDict(dict):
- class Iterator:
- def __init__(self, sorted_dict):
- self._dict = sorted_dict
- self._keys = sorted(self._dict.keys())
- self._nr_items = len(self._keys)
- self._idx = 0
-
- def __iter__(self):
- return self
-
- def next(self):
- if self._idx >= self._nr_items:
- raise StopIteration
-
- key = self._keys[self._idx]
- value = self._dict[key]
- self._idx += 1
-
- return key, value
-
- __next__ = next
-
- def __iter__(self):
- return SortedDict.Iterator(self)
-
- iterkeys = __iter__
-
-
-class AddressBook(QWidget):
- def __init__(self, parent=None):
- super().__init__(parent)
-
- self.contacts = SortedDict()
- self._old_name = ''
- self._old_address = ''
-
- name_label = QLabel("Name:")
- self._name_line = QLineEdit()
- self._name_line.setReadOnly(True)
-
- address_label = QLabel("Address:")
- self._address_text = QTextEdit()
- self._address_text.setReadOnly(True)
-
- self._add_button = QPushButton("&Add")
- self._submit_button = QPushButton("&Submit")
- self._submit_button.hide()
- self._cancel_button = QPushButton("&Cancel")
- self._cancel_button.hide()
- self._next_button = QPushButton("&Next")
- self._next_button.setEnabled(False)
- self._previous_button = QPushButton("&Previous")
- self._previous_button.setEnabled(False)
-
- self._add_button.clicked.connect(self.add_contact)
- self._submit_button.clicked.connect(self.submit_contact)
- self._cancel_button.clicked.connect(self.cancel)
- self._next_button.clicked.connect(self.next)
- self._previous_button.clicked.connect(self.previous)
-
- button_layout_1 = QVBoxLayout()
- button_layout_1.addWidget(self._add_button, Qt.AlignmentFlag.AlignTop)
- button_layout_1.addWidget(self._submit_button)
- button_layout_1.addWidget(self._cancel_button)
- button_layout_1.addStretch()
-
- button_layout_2 = QHBoxLayout()
- button_layout_2.addWidget(self._previous_button)
- button_layout_2.addWidget(self._next_button)
-
- main_layout = QGridLayout()
- main_layout.addWidget(name_label, 0, 0)
- main_layout.addWidget(self._name_line, 0, 1)
- main_layout.addWidget(address_label, 1, 0, Qt.AlignmentFlag.AlignTop)
- main_layout.addWidget(self._address_text, 1, 1)
- main_layout.addLayout(button_layout_1, 1, 2)
- main_layout.addLayout(button_layout_2, 3, 1)
-
- self.setLayout(main_layout)
- self.setWindowTitle("Simple Address Book")
-
- def add_contact(self):
- self._old_name = self._name_line.text()
- self._old_address = self._address_text.toPlainText()
-
- self._name_line.clear()
- self._address_text.clear()
-
- self._name_line.setReadOnly(False)
- self._name_line.setFocus(Qt.FocusReason.OtherFocusReason)
- self._address_text.setReadOnly(False)
-
- self._add_button.setEnabled(False)
- self._next_button.setEnabled(False)
- self._previous_button.setEnabled(False)
- self._submit_button.show()
- self._cancel_button.show()
-
- @Slot()
- def submit_contact(self):
- name = self._name_line.text()
- address = self._address_text.toPlainText()
-
- if name == "" or address == "":
- QMessageBox.information(self, "Empty Field", "Please enter a name and address.")
- return
-
- if name not in self.contacts:
- self.contacts[name] = address
- QMessageBox.information(self, "Add Successful",
- f'"{name}" has been added to your address book.')
- else:
- QMessageBox.information(self, "Add Unsuccessful",
- f'Sorry, "{name}" is already in your address book.')
- return
-
- if not self.contacts:
- self._name_line.clear()
- self._address_text.clear()
-
- self._name_line.setReadOnly(True)
- self._address_text.setReadOnly(True)
- self._add_button.setEnabled(True)
-
- number = len(self.contacts)
- self._next_button.setEnabled(number > 1)
- self._previous_button.setEnabled(number > 1)
-
- self._submit_button.hide()
- self._cancel_button.hide()
-
- @Slot()
- def cancel(self):
- self._name_line.setText(self._old_name)
- self._address_text.setText(self._old_address)
-
- if not self.contacts:
- self._name_line.clear()
- self._address_text.clear()
-
- self._name_line.setReadOnly(True)
- self._address_text.setReadOnly(True)
- self._add_button.setEnabled(True)
-
- number = len(self.contacts)
- self._next_button.setEnabled(number > 1)
- self._previous_button.setEnabled(number > 1)
-
- self._submit_button.hide()
- self._cancel_button.hide()
-
- @Slot()
- def next(self):
- name = self._name_line.text()
- it = iter(self.contacts)
-
- try:
- while True:
- this_name, _ = it.next()
-
- if this_name == name:
- next_name, next_address = it.next()
- break
- except StopIteration:
- next_name, next_address = iter(self.contacts).next()
-
- self._name_line.setText(next_name)
- self._address_text.setText(next_address)
-
- @Slot()
- def previous(self):
- name = self._name_line.text()
-
- prev_name = prev_address = None
- for this_name, this_address in self.contacts:
- if this_name == name:
- break
-
- prev_name = this_name
- prev_address = this_address
- else:
- self._name_line.clear()
- self._address_text.clear()
- return
-
- if prev_name is None:
- for prev_name, prev_address in self.contacts:
- pass
-
- self._name_line.setText(prev_name)
- self._address_text.setText(prev_address)
-
-
-if __name__ == '__main__':
- app = QApplication(sys.argv)
-
- address_book = AddressBook()
- address_book.show()
-
- sys.exit(app.exec())
+++ /dev/null
-# Copyright (C) 2013 Riverbank Computing Limited.
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from __future__ import annotations
-
-import sys
-
-from PySide6.QtCore import Qt, Slot
-from PySide6.QtWidgets import (QApplication, QGridLayout,
- QHBoxLayout, QLabel, QLineEdit,
- QMessageBox, QPushButton, QTextEdit,
- QVBoxLayout, QWidget)
-
-
-class SortedDict(dict):
- class Iterator:
- def __init__(self, sorted_dict):
- self._dict = sorted_dict
- self._keys = sorted(self._dict.keys())
- self._nr_items = len(self._keys)
- self._idx = 0
-
- def __iter__(self):
- return self
-
- def next(self):
- if self._idx >= self._nr_items:
- raise StopIteration
-
- key = self._keys[self._idx]
- value = self._dict[key]
- self._idx += 1
-
- return key, value
-
- __next__ = next
-
- def __iter__(self):
- return SortedDict.Iterator(self)
-
- iterkeys = __iter__
-
-
-class AddressBook(QWidget):
- NavigationMode, AddingMode, EditingMode = range(3)
-
- def __init__(self, parent=None):
- super().__init__(parent)
-
- self.contacts = SortedDict()
- self._old_name = ''
- self._old_address = ''
- self._current_mode = self.NavigationMode
-
- name_label = QLabel("Name:")
- self._name_line = QLineEdit()
- self._name_line.setReadOnly(True)
-
- address_label = QLabel("Address:")
- self._address_text = QTextEdit()
- self._address_text.setReadOnly(True)
-
- self._add_button = QPushButton("&Add")
- self._edit_button = QPushButton("&Edit")
- self._edit_button.setEnabled(False)
- self._remove_button = QPushButton("&Remove")
- self._remove_button.setEnabled(False)
- self._submit_button = QPushButton("&Submit")
- self._submit_button.hide()
- self._cancel_button = QPushButton("&Cancel")
- self._cancel_button.hide()
-
- self._next_button = QPushButton("&Next")
- self._next_button.setEnabled(False)
- self._previous_button = QPushButton("&Previous")
- self._previous_button.setEnabled(False)
-
- self._add_button.clicked.connect(self.add_contact)
- self._submit_button.clicked.connect(self.submit_contact)
- self._edit_button.clicked.connect(self.edit_contact)
- self._remove_button.clicked.connect(self.remove_contact)
- self._cancel_button.clicked.connect(self.cancel)
- self._next_button.clicked.connect(self.next)
- self._previous_button.clicked.connect(self.previous)
-
- button_layout_1 = QVBoxLayout()
- button_layout_1.addWidget(self._add_button)
- button_layout_1.addWidget(self._edit_button)
- button_layout_1.addWidget(self._remove_button)
- button_layout_1.addWidget(self._submit_button)
- button_layout_1.addWidget(self._cancel_button)
- button_layout_1.addStretch()
-
- button_layout_2 = QHBoxLayout()
- button_layout_2.addWidget(self._previous_button)
- button_layout_2.addWidget(self._next_button)
-
- main_layout = QGridLayout()
- main_layout.addWidget(name_label, 0, 0)
- main_layout.addWidget(self._name_line, 0, 1)
- main_layout.addWidget(address_label, 1, 0, Qt.AlignmentFlag.AlignTop)
- main_layout.addWidget(self._address_text, 1, 1)
- main_layout.addLayout(button_layout_1, 1, 2)
- main_layout.addLayout(button_layout_2, 3, 1)
-
- self.setLayout(main_layout)
- self.setWindowTitle("Simple Address Book")
-
- @Slot()
- def add_contact(self):
- self._old_name = self._name_line.text()
- self._old_address = self._address_text.toPlainText()
-
- self._name_line.clear()
- self._address_text.clear()
-
- self.update_interface(self.AddingMode)
-
- @Slot()
- def edit_contact(self):
- self._old_name = self._name_line.text()
- self._old_address = self._address_text.toPlainText()
-
- self.update_interface(self.EditingMode)
-
- @Slot()
- def submit_contact(self):
- name = self._name_line.text()
- address = self._address_text.toPlainText()
-
- if name == "" or address == "":
- QMessageBox.information(self, "Empty Field", "Please enter a name and address.")
- return
-
- if self._current_mode == self.AddingMode:
- if name not in self.contacts:
- self.contacts[name] = address
- QMessageBox.information(self, "Add Successful",
- f'"{name}" has been added to your address book.')
- else:
- QMessageBox.information(self, "Add Unsuccessful",
- f'Sorry, "{name}" is already in your address book.')
- return
-
- elif self._current_mode == self.EditingMode:
- if self._old_name != name:
- if name not in self.contacts:
- QMessageBox.information(self, "Edit Successful",
- f'"{self.oldName}" has been edited in your '
- 'address book.')
- del self.contacts[self._old_name]
- self.contacts[name] = address
- else:
- QMessageBox.information(self, "Edit Unsuccessful",
- f'Sorry, "{name}" is already in your address book.')
- return
- elif self._old_address != address:
- QMessageBox.information(self, "Edit Successful",
- f'"{name}" has been edited in your address book.')
- self.contacts[name] = address
-
- self.update_interface(self.NavigationMode)
-
- @Slot()
- def cancel(self):
- self._name_line.setText(self._old_name)
- self._address_text.setText(self._old_address)
- self.update_interface(self.NavigationMode)
-
- @Slot()
- def remove_contact(self):
- name = self._name_line.text()
-
- if name in self.contacts:
- button = QMessageBox.question(self, "Confirm Remove",
- f'Are you sure you want to remove "{name}"?',
- QMessageBox.Yes | QMessageBox.No)
-
- if button == QMessageBox.Yes:
- self.previous()
- del self.contacts[name]
-
- QMessageBox.information(self, "Remove Successful",
- f'"{name}" has been removed from your address book.')
-
- self.update_interface(self.NavigationMode)
-
- @Slot()
- def next(self):
- name = self._name_line.text()
- it = iter(self.contacts)
-
- try:
- while True:
- this_name, _ = it.next()
-
- if this_name == name:
- next_name, next_address = it.next()
- break
- except StopIteration:
- next_name, next_address = iter(self.contacts).next()
-
- self._name_line.setText(next_name)
- self._address_text.setText(next_address)
-
- @Slot()
- def previous(self):
- name = self._name_line.text()
-
- prev_name = prev_address = None
- for this_name, this_address in self.contacts:
- if this_name == name:
- break
-
- prev_name = this_name
- prev_address = this_address
- else:
- self._name_line.clear()
- self._address_text.clear()
- return
-
- if prev_name is None:
- for prev_name, prev_address in self.contacts:
- pass
-
- self._name_line.setText(prev_name)
- self._address_text.setText(prev_address)
-
- def update_interface(self, mode):
- self._current_mode = mode
-
- if self._current_mode in (self.AddingMode, self.EditingMode):
- self._name_line.setReadOnly(False)
- self._name_line.setFocus(Qt.FocusReason.OtherFocusReason)
- self._address_text.setReadOnly(False)
-
- self._add_button.setEnabled(False)
- self._edit_button.setEnabled(False)
- self._remove_button.setEnabled(False)
-
- self._next_button.setEnabled(False)
- self._previous_button.setEnabled(False)
-
- self._submit_button.show()
- self._cancel_button.show()
-
- elif self._current_mode == self.NavigationMode:
- if not self.contacts:
- self._name_line.clear()
- self._address_text.clear()
-
- self._name_line.setReadOnly(True)
- self._address_text.setReadOnly(True)
- self._add_button.setEnabled(True)
-
- number = len(self.contacts)
- self._edit_button.setEnabled(number >= 1)
- self._remove_button.setEnabled(number >= 1)
- self._next_button.setEnabled(number > 1)
- self._previous_button.setEnabled(number > 1)
-
- self._submit_button.hide()
- self._cancel_button.hide()
-
-
-if __name__ == '__main__':
- app = QApplication(sys.argv)
-
- address_book = AddressBook()
- address_book.show()
-
- sys.exit(app.exec())
+++ /dev/null
-# Copyright (C) 2013 Riverbank Computing Limited.
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from __future__ import annotations
-
-import sys
-
-from PySide6.QtCore import Qt, Slot
-from PySide6.QtWidgets import (QApplication, QDialog, QGridLayout,
- QHBoxLayout, QLabel, QLineEdit,
- QMessageBox, QPushButton, QTextEdit,
- QVBoxLayout, QWidget)
-
-
-class SortedDict(dict):
- class Iterator:
- def __init__(self, sorted_dict):
- self._dict = sorted_dict
- self._keys = sorted(self._dict.keys())
- self._nr_items = len(self._keys)
- self._idx = 0
-
- def __iter__(self):
- return self
-
- def next(self):
- if self._idx >= self._nr_items:
- raise StopIteration
-
- key = self._keys[self._idx]
- value = self._dict[key]
- self._idx += 1
-
- return key, value
-
- __next__ = next
-
- def __iter__(self):
- return SortedDict.Iterator(self)
-
- iterkeys = __iter__
-
-
-class AddressBook(QWidget):
- NavigationMode, AddingMode, EditingMode = range(3)
-
- def __init__(self, parent=None):
- super().__init__(parent)
-
- self.contacts = SortedDict()
- self._old_name = ''
- self._old_address = ''
- self._current_mode = self.NavigationMode
-
- name_label = QLabel("Name:")
- self._name_line = QLineEdit()
- self._name_line.setReadOnly(True)
-
- address_label = QLabel("Address:")
- self._address_text = QTextEdit()
- self._address_text.setReadOnly(True)
-
- self._add_button = QPushButton("&Add")
- self._edit_button = QPushButton("&Edit")
- self._edit_button.setEnabled(False)
- self._remove_button = QPushButton("&Remove")
- self._remove_button.setEnabled(False)
- self._find_button = QPushButton("&Find")
- self._find_button.setEnabled(False)
- self._submit_button = QPushButton("&Submit")
- self._submit_button.hide()
- self._cancel_button = QPushButton("&Cancel")
- self._cancel_button.hide()
-
- self._next_button = QPushButton("&Next")
- self._next_button.setEnabled(False)
- self._previous_button = QPushButton("&Previous")
- self._previous_button.setEnabled(False)
-
- self.dialog = FindDialog()
-
- self._add_button.clicked.connect(self.add_contact)
- self._submit_button.clicked.connect(self.submit_contact)
- self._edit_button.clicked.connect(self.edit_contact)
- self._remove_button.clicked.connect(self.remove_contact)
- self._find_button.clicked.connect(self.find_contact)
- self._cancel_button.clicked.connect(self.cancel)
- self._next_button.clicked.connect(self.next)
- self._previous_button.clicked.connect(self.previous)
-
- button_layout_1 = QVBoxLayout()
- button_layout_1.addWidget(self._add_button)
- button_layout_1.addWidget(self._edit_button)
- button_layout_1.addWidget(self._remove_button)
- button_layout_1.addWidget(self._find_button)
- button_layout_1.addWidget(self._submit_button)
- button_layout_1.addWidget(self._cancel_button)
- button_layout_1.addStretch()
-
- button_layout_2 = QHBoxLayout()
- button_layout_2.addWidget(self._previous_button)
- button_layout_2.addWidget(self._next_button)
-
- main_layout = QGridLayout()
- main_layout.addWidget(name_label, 0, 0)
- main_layout.addWidget(self._name_line, 0, 1)
- main_layout.addWidget(address_label, 1, 0, Qt.AlignmentFlag.AlignTop)
- main_layout.addWidget(self._address_text, 1, 1)
- main_layout.addLayout(button_layout_1, 1, 2)
- main_layout.addLayout(button_layout_2, 2, 1)
-
- self.setLayout(main_layout)
- self.setWindowTitle("Simple Address Book")
-
- @Slot()
- def add_contact(self):
- self._old_name = self._name_line.text()
- self._old_address = self._address_text.toPlainText()
-
- self._name_line.clear()
- self._address_text.clear()
-
- self.update_interface(self.AddingMode)
-
- @Slot()
- def edit_contact(self):
- self._old_name = self._name_line.text()
- self._old_address = self._address_text.toPlainText()
-
- self.update_interface(self.EditingMode)
-
- @Slot()
- def submit_contact(self):
- name = self._name_line.text()
- address = self._address_text.toPlainText()
-
- if name == "" or address == "":
- QMessageBox.information(self, "Empty Field", "Please enter a name and address.")
- return
-
- if self._current_mode == self.AddingMode:
- if name not in self.contacts:
- self.contacts[name] = address
- QMessageBox.information(self, "Add Successful",
- f'"{name}" has been added to your address book.')
- else:
- QMessageBox.information(self, "Add Unsuccessful",
- f'Sorry, "{name}" is already in your address book.')
- return
-
- elif self._current_mode == self.EditingMode:
- if self._old_name != name:
- if name not in self.contacts:
- QMessageBox.information(self, "Edit Successful",
- f'"{self.oldName}" has been edited in your '
- 'address book.')
- del self.contacts[self._old_name]
- self.contacts[name] = address
- else:
- QMessageBox.information(self, "Edit Unsuccessful",
- f'Sorry, "{name}" is already in your address book.')
- return
- elif self._old_address != address:
- QMessageBox.information(self, "Edit Successful",
- f'"{name}" has been edited in your address book.')
- self.contacts[name] = address
-
- self.update_interface(self.NavigationMode)
-
- @Slot()
- def cancel(self):
- self._name_line.setText(self._old_name)
- self._address_text.setText(self._old_address)
- self.update_interface(self.NavigationMode)
-
- @Slot()
- def remove_contact(self):
- name = self._name_line.text()
-
- if name in self.contacts:
- button = QMessageBox.question(self, "Confirm Remove",
- f'Are you sure you want to remove "{name}"?',
- QMessageBox.Yes | QMessageBox.No)
-
- if button == QMessageBox.Yes:
- self.previous()
- del self.contacts[name]
-
- QMessageBox.information(self, "Remove Successful",
- f'"{name}" has been removed from your address book.')
-
- self.update_interface(self.NavigationMode)
-
- @Slot()
- def next(self):
- name = self._name_line.text()
- it = iter(self.contacts)
-
- try:
- while True:
- this_name, _ = it.next()
-
- if this_name == name:
- next_name, next_address = it.next()
- break
- except StopIteration:
- next_name, next_address = iter(self.contacts).next()
-
- self._name_line.setText(next_name)
- self._address_text.setText(next_address)
-
- @Slot()
- def previous(self):
- name = self._name_line.text()
-
- prev_name = prev_address = None
- for this_name, this_address in self.contacts:
- if this_name == name:
- break
-
- prev_name = this_name
- prev_address = this_address
- else:
- self._name_line.clear()
- self._address_text.clear()
- return
-
- if prev_name is None:
- for prev_name, prev_address in self.contacts:
- pass
-
- self._name_line.setText(prev_name)
- self._address_text.setText(prev_address)
-
- def find_contact(self):
- self.dialog.show()
-
- if self.dialog.exec() == QDialog.Accepted:
- contact_name = self.dialog.get_find_text()
-
- if contact_name in self.contacts:
- self._name_line.setText(contact_name)
- self._address_text.setText(self.contacts[contact_name])
- else:
- QMessageBox.information(self, "Contact Not Found",
- f'Sorry, "{contact_name}" is not in your address book.')
- return
-
- self.update_interface(self.NavigationMode)
-
- def update_interface(self, mode):
- self._current_mode = mode
-
- if self._current_mode in (self.AddingMode, self.EditingMode):
- self._name_line.setReadOnly(False)
- self._name_line.setFocus(Qt.FocusReason.OtherFocusReason)
- self._address_text.setReadOnly(False)
-
- self._add_button.setEnabled(False)
- self._edit_button.setEnabled(False)
- self._remove_button.setEnabled(False)
-
- self._next_button.setEnabled(False)
- self._previous_button.setEnabled(False)
-
- self._submit_button.show()
- self._cancel_button.show()
-
- elif self._current_mode == self.NavigationMode:
- if not self.contacts:
- self._name_line.clear()
- self._address_text.clear()
-
- self._name_line.setReadOnly(True)
- self._address_text.setReadOnly(True)
- self._add_button.setEnabled(True)
-
- number = len(self.contacts)
- self._edit_button.setEnabled(number >= 1)
- self._remove_button.setEnabled(number >= 1)
- self._find_button.setEnabled(number > 2)
- self._next_button.setEnabled(number > 1)
- self._previous_button.setEnabled(number > 1)
-
- self._submit_button.hide()
- self._cancel_button.hide()
-
-
-class FindDialog(QDialog):
- def __init__(self, parent=None):
- super().__init__(parent)
-
- find_label = QLabel("Enter the name of a contact:")
- self._line_edit = QLineEdit()
-
- self._find_button = QPushButton("&Find")
- self._find_text = ''
-
- layout = QHBoxLayout()
- layout.addWidget(find_label)
- layout.addWidget(self._line_edit)
- layout.addWidget(self._find_button)
-
- self.setLayout(layout)
- self.setWindowTitle("Find a Contact")
-
- self._find_button.clicked.connect(self.find_clicked)
- self._find_button.clicked.connect(self.accept)
-
- def find_clicked(self):
- text = self._line_edit.text()
-
- if not text:
- QMessageBox.information(self, "Empty Field", "Please enter a name.")
- return
- else:
- self._find_text = text
- self._line_edit.clear()
- self.hide()
-
- def get_find_text(self):
- return self._find_text
-
-
-if __name__ == '__main__':
- app = QApplication(sys.argv)
-
- address_book = AddressBook()
- address_book.show()
-
- sys.exit(app.exec())
+++ /dev/null
-# Copyright (C) 2013 Riverbank Computing Limited.
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from __future__ import annotations
-
-import pickle
-import sys
-
-from PySide6.QtCore import Qt, Slot
-from PySide6.QtWidgets import (QApplication, QDialog, QFileDialog,
- QGridLayout, QHBoxLayout, QLabel, QLineEdit,
- QMessageBox, QPushButton, QTextEdit,
- QVBoxLayout, QWidget)
-
-
-class SortedDict(dict):
- class Iterator:
- def __init__(self, sorted_dict):
- self._dict = sorted_dict
- self._keys = sorted(self._dict.keys())
- self._nr_items = len(self._keys)
- self._idx = 0
-
- def __iter__(self):
- return self
-
- def next(self):
- if self._idx >= self._nr_items:
- raise StopIteration
-
- key = self._keys[self._idx]
- value = self._dict[key]
- self._idx += 1
-
- return key, value
-
- __next__ = next
-
- def __iter__(self):
- return SortedDict.Iterator(self)
-
- iterkeys = __iter__
-
-
-class AddressBook(QWidget):
- NavigationMode, AddingMode, EditingMode = range(3)
-
- def __init__(self, parent=None):
- super().__init__(parent)
-
- self.contacts = SortedDict()
- self._old_name = ''
- self._old_address = ''
- self._current_mode = self.NavigationMode
-
- name_label = QLabel("Name:")
- self._name_line = QLineEdit()
- self._name_line.setReadOnly(True)
-
- address_label = QLabel("Address:")
- self._address_text = QTextEdit()
- self._address_text.setReadOnly(True)
-
- self._add_button = QPushButton("&Add")
- self._edit_button = QPushButton("&Edit")
- self._edit_button.setEnabled(False)
- self._remove_button = QPushButton("&Remove")
- self._remove_button.setEnabled(False)
- self._find_button = QPushButton("&Find")
- self._find_button.setEnabled(False)
- self._submit_button = QPushButton("&Submit")
- self._submit_button.hide()
- self._cancel_button = QPushButton("&Cancel")
- self._cancel_button.hide()
-
- self._next_button = QPushButton("&Next")
- self._next_button.setEnabled(False)
- self._previous_button = QPushButton("&Previous")
- self._previous_button.setEnabled(False)
-
- self._load_button = QPushButton("&Load...")
- self._load_button.setToolTip("Load contacts from a file")
- self._save_button = QPushButton("Sa&ve...")
- self._save_button.setToolTip("Save contacts to a file")
- self._save_button.setEnabled(False)
-
- self.dialog = FindDialog()
-
- self._add_button.clicked.connect(self.add_contact)
- self._submit_button.clicked.connect(self.submit_contact)
- self._edit_button.clicked.connect(self.edit_contact)
- self._remove_button.clicked.connect(self.remove_contact)
- self._find_button.clicked.connect(self.find_contact)
- self._cancel_button.clicked.connect(self.cancel)
- self._next_button.clicked.connect(self.next)
- self._previous_button.clicked.connect(self.previous)
- self._load_button.clicked.connect(self.load_from_file)
- self._save_button.clicked.connect(self.save_to_file)
-
- button_layout_1 = QVBoxLayout()
- button_layout_1.addWidget(self._add_button)
- button_layout_1.addWidget(self._edit_button)
- button_layout_1.addWidget(self._remove_button)
- button_layout_1.addWidget(self._find_button)
- button_layout_1.addWidget(self._submit_button)
- button_layout_1.addWidget(self._cancel_button)
- button_layout_1.addWidget(self._load_button)
- button_layout_1.addWidget(self._save_button)
- button_layout_1.addStretch()
-
- button_layout_2 = QHBoxLayout()
- button_layout_2.addWidget(self._previous_button)
- button_layout_2.addWidget(self._next_button)
-
- main_layout = QGridLayout()
- main_layout.addWidget(name_label, 0, 0)
- main_layout.addWidget(self._name_line, 0, 1)
- main_layout.addWidget(address_label, 1, 0, Qt.AlignmentFlag.AlignTop)
- main_layout.addWidget(self._address_text, 1, 1)
- main_layout.addLayout(button_layout_1, 1, 2)
- main_layout.addLayout(button_layout_2, 2, 1)
-
- self.setLayout(main_layout)
- self.setWindowTitle("Simple Address Book")
-
- @Slot()
- def add_contact(self):
- self._old_name = self._name_line.text()
- self._old_address = self._address_text.toPlainText()
-
- self._name_line.clear()
- self._address_text.clear()
-
- self.update_interface(self.AddingMode)
-
- @Slot()
- def edit_contact(self):
- self._old_name = self._name_line.text()
- self._old_address = self._address_text.toPlainText()
-
- self.update_interface(self.EditingMode)
-
- @Slot()
- def submit_contact(self):
- name = self._name_line.text()
- address = self._address_text.toPlainText()
-
- if name == "" or address == "":
- QMessageBox.information(self, "Empty Field", "Please enter a name and address.")
- return
-
- if self._current_mode == self.AddingMode:
- if name not in self.contacts:
- self.contacts[name] = address
- QMessageBox.information(self, "Add Successful",
- f'"{name}" has been added to your address book.')
- else:
- QMessageBox.information(self, "Add Unsuccessful",
- f'Sorry, "{name}" is already in your address book.')
- return
-
- elif self._current_mode == self.EditingMode:
- if self._old_name != name:
- if name not in self.contacts:
- QMessageBox.information(self, "Edit Successful",
- f'"{self.oldName}" has been edited in your '
- 'address book.')
- del self.contacts[self._old_name]
- self.contacts[name] = address
- else:
- QMessageBox.information(self, "Edit Unsuccessful",
- f'Sorry, "{name}" is already in your address book.')
- return
- elif self._old_address != address:
- QMessageBox.information(self, "Edit Successful",
- f'"{name}" has been edited in your address book.')
- self.contacts[name] = address
-
- self.update_interface(self.NavigationMode)
-
- @Slot()
- def cancel(self):
- self._name_line.setText(self._old_name)
- self._address_text.setText(self._old_address)
- self.update_interface(self.NavigationMode)
-
- @Slot()
- def remove_contact(self):
- name = self._name_line.text()
-
- if name in self.contacts:
- button = QMessageBox.question(self, "Confirm Remove",
- f'Are you sure you want to remove "{name}"?',
- QMessageBox.Yes | QMessageBox.No)
-
- if button == QMessageBox.Yes:
- self.previous()
- del self.contacts[name]
-
- QMessageBox.information(self, "Remove Successful",
- f'"{name}" has been removed from your address book.')
-
- self.update_interface(self.NavigationMode)
-
- @Slot()
- def next(self):
- name = self._name_line.text()
- it = iter(self.contacts)
-
- try:
- while True:
- this_name, _ = it.next()
-
- if this_name == name:
- next_name, next_address = it.next()
- break
- except StopIteration:
- next_name, next_address = iter(self.contacts).next()
-
- self._name_line.setText(next_name)
- self._address_text.setText(next_address)
-
- @Slot()
- def previous(self):
- name = self._name_line.text()
-
- prev_name = prev_address = None
- for this_name, this_address in self.contacts:
- if this_name == name:
- break
-
- prev_name = this_name
- prev_address = this_address
- else:
- self._name_line.clear()
- self._address_text.clear()
- return
-
- if prev_name is None:
- for prev_name, prev_address in self.contacts:
- pass
-
- self._name_line.setText(prev_name)
- self._address_text.setText(prev_address)
-
- def find_contact(self):
- self.dialog.show()
-
- if self.dialog.exec() == QDialog.Accepted:
- contact_name = self.dialog.get_find_text()
-
- if contact_name in self.contacts:
- self._name_line.setText(contact_name)
- self._address_text.setText(self.contacts[contact_name])
- else:
- QMessageBox.information(self, "Contact Not Found",
- f'Sorry, "{contact_name}" is not in your address book.')
- return
-
- self.update_interface(self.NavigationMode)
-
- def update_interface(self, mode):
- self._current_mode = mode
-
- if self._current_mode in (self.AddingMode, self.EditingMode):
- self._name_line.setReadOnly(False)
- self._name_line.setFocus(Qt.FocusReason.OtherFocusReason)
- self._address_text.setReadOnly(False)
-
- self._add_button.setEnabled(False)
- self._edit_button.setEnabled(False)
- self._remove_button.setEnabled(False)
-
- self._next_button.setEnabled(False)
- self._previous_button.setEnabled(False)
-
- self._submit_button.show()
- self._cancel_button.show()
-
- self._load_button.setEnabled(False)
- self._save_button.setEnabled(False)
-
- elif self._current_mode == self.NavigationMode:
- if not self.contacts:
- self._name_line.clear()
- self._address_text.clear()
-
- self._name_line.setReadOnly(True)
- self._address_text.setReadOnly(True)
- self._add_button.setEnabled(True)
-
- number = len(self.contacts)
- self._edit_button.setEnabled(number >= 1)
- self._remove_button.setEnabled(number >= 1)
- self._find_button.setEnabled(number > 2)
- self._next_button.setEnabled(number > 1)
- self._previous_button.setEnabled(number > 1)
-
- self._submit_button.hide()
- self._cancel_button.hide()
-
- self._load_button.setEnabled(True)
- self._save_button.setEnabled(number >= 1)
-
- def save_to_file(self):
- fileName, _ = QFileDialog.getSaveFileName(self,
- "Save Address Book", '',
- "Address Book (*.abk);;All Files (*)")
-
- if not fileName:
- return
-
- try:
- out_file = open(str(fileName), 'wb')
- except IOError:
- QMessageBox.information(self, "Unable to open file",
- f'There was an error opening "{fileName}"')
- return
-
- pickle.dump(self.contacts, out_file)
- out_file.close()
-
- def load_from_file(self):
- fileName, _ = QFileDialog.getOpenFileName(self,
- "Open Address Book", '',
- "Address Book (*.abk);;All Files (*)")
-
- if not fileName:
- return
-
- try:
- in_file = open(str(fileName), 'rb')
- except IOError:
- QMessageBox.information(self, "Unable to open file",
- f'There was an error opening "{fileName}"')
- return
-
- self.contacts = pickle.load(in_file)
- in_file.close()
-
- if len(self.contacts) == 0:
- QMessageBox.information(self, "No contacts in file",
- "The file you are attempting to open contains no contacts.")
- else:
- for name, address in self.contacts:
- self._name_line.setText(name)
- self._address_text.setText(address)
-
- self.update_interface(self.NavigationMode)
-
-
-class FindDialog(QDialog):
- def __init__(self, parent=None):
- super().__init__(parent)
-
- find_label = QLabel("Enter the name of a contact:")
- self._line_edit = QLineEdit()
-
- self._find_button = QPushButton("&Find")
- self._find_text = ''
-
- layout = QHBoxLayout()
- layout.addWidget(find_label)
- layout.addWidget(self._line_edit)
- layout.addWidget(self._find_button)
-
- self.setLayout(layout)
- self.setWindowTitle("Find a Contact")
-
- self._find_button.clicked.connect(self.find_clicked)
- self._find_button.clicked.connect(self.accept)
-
- def find_clicked(self):
- text = self._line_edit.text()
-
- if not text:
- QMessageBox.information(self, "Empty Field", "Please enter a name.")
- return
-
- self._find_text = text
- self._line_edit.clear()
- self.hide()
-
- def get_find_text(self):
- return self._find_text
-
-
-if __name__ == '__main__':
- app = QApplication(sys.argv)
-
- address_book = AddressBook()
- address_book.show()
-
- sys.exit(app.exec())
+++ /dev/null
-# Copyright (C) 2013 Riverbank Computing Limited.
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-from __future__ import annotations
-
-import pickle
-import sys
-
-from PySide6.QtCore import QFile, QIODevice, QTextStream, Qt, Slot
-from PySide6.QtWidgets import (QApplication, QDialog, QFileDialog,
- QGridLayout, QHBoxLayout, QLabel, QLineEdit,
- QMessageBox, QPushButton, QTextEdit,
- QVBoxLayout, QWidget)
-
-
-class SortedDict(dict):
- class Iterator:
- def __init__(self, sorted_dict):
- self._dict = sorted_dict
- self._keys = sorted(self._dict.keys())
- self._nr_items = len(self._keys)
- self._idx = 0
-
- def __iter__(self):
- return self
-
- def next(self):
- if self._idx >= self._nr_items:
- raise StopIteration
-
- key = self._keys[self._idx]
- value = self._dict[key]
- self._idx += 1
-
- return key, value
-
- __next__ = next
-
- def __iter__(self):
- return SortedDict.Iterator(self)
-
- iterkeys = __iter__
-
-
-class AddressBook(QWidget):
- NavigationMode, AddingMode, EditingMode = range(3)
-
- def __init__(self, parent=None):
- super().__init__(parent)
-
- self.contacts = SortedDict()
- self._old_name = ''
- self._old_address = ''
- self._current_mode = self.NavigationMode
-
- name_label = QLabel("Name:")
- self._name_line = QLineEdit()
- self._name_line.setReadOnly(True)
-
- address_label = QLabel("Address:")
- self._address_text = QTextEdit()
- self._address_text.setReadOnly(True)
-
- self._add_button = QPushButton("&Add")
- self._edit_button = QPushButton("&Edit")
- self._edit_button.setEnabled(False)
- self._remove_button = QPushButton("&Remove")
- self._remove_button.setEnabled(False)
- self._find_button = QPushButton("&Find")
- self._find_button.setEnabled(False)
- self._submit_button = QPushButton("&Submit")
- self._submit_button.hide()
- self._cancel_button = QPushButton("&Cancel")
- self._cancel_button.hide()
-
- self._next_button = QPushButton("&Next")
- self._next_button.setEnabled(False)
- self._previous_button = QPushButton("&Previous")
- self._previous_button.setEnabled(False)
-
- self._load_button = QPushButton("&Load...")
- self._load_button.setToolTip("Load contacts from a file")
- self._save_button = QPushButton("Sa&ve...")
- self._save_button.setToolTip("Save contacts to a file")
- self._save_button.setEnabled(False)
-
- self._export_button = QPushButton("Ex&port")
- self._export_button.setToolTip("Export as vCard")
- self._export_button.setEnabled(False)
-
- self.dialog = FindDialog()
-
- self._add_button.clicked.connect(self.add_contact)
- self._submit_button.clicked.connect(self.submit_contact)
- self._edit_button.clicked.connect(self.edit_contact)
- self._remove_button.clicked.connect(self.remove_contact)
- self._find_button.clicked.connect(self.find_contact)
- self._cancel_button.clicked.connect(self.cancel)
- self._next_button.clicked.connect(self.next)
- self._previous_button.clicked.connect(self.previous)
- self._load_button.clicked.connect(self.load_from_file)
- self._save_button.clicked.connect(self.save_to_file)
- self._export_button.clicked.connect(self.export_as_vcard)
-
- button_layout_1 = QVBoxLayout()
- button_layout_1.addWidget(self._add_button)
- button_layout_1.addWidget(self._edit_button)
- button_layout_1.addWidget(self._remove_button)
- button_layout_1.addWidget(self._find_button)
- button_layout_1.addWidget(self._submit_button)
- button_layout_1.addWidget(self._cancel_button)
- button_layout_1.addWidget(self._load_button)
- button_layout_1.addWidget(self._save_button)
- button_layout_1.addWidget(self._export_button)
- button_layout_1.addStretch()
-
- button_layout_2 = QHBoxLayout()
- button_layout_2.addWidget(self._previous_button)
- button_layout_2.addWidget(self._next_button)
-
- main_layout = QGridLayout()
- main_layout.addWidget(name_label, 0, 0)
- main_layout.addWidget(self._name_line, 0, 1)
- main_layout.addWidget(address_label, 1, 0, Qt.AlignmentFlag.AlignTop)
- main_layout.addWidget(self._address_text, 1, 1)
- main_layout.addLayout(button_layout_1, 1, 2)
- main_layout.addLayout(button_layout_2, 2, 1)
-
- self.setLayout(main_layout)
- self.setWindowTitle("Simple Address Book")
-
- @Slot()
- def add_contact(self):
- self._old_name = self._name_line.text()
- self._old_address = self._address_text.toPlainText()
-
- self._name_line.clear()
- self._address_text.clear()
-
- self.update_interface(self.AddingMode)
-
- @Slot()
- def edit_contact(self):
- self._old_name = self._name_line.text()
- self._old_address = self._address_text.toPlainText()
-
- self.update_interface(self.EditingMode)
-
- @Slot()
- def submit_contact(self):
- name = self._name_line.text()
- address = self._address_text.toPlainText()
-
- if name == "" or address == "":
- QMessageBox.information(self, "Empty Field", "Please enter a name and address.")
- return
-
- if self._current_mode == self.AddingMode:
- if name not in self.contacts:
- self.contacts[name] = address
- QMessageBox.information(self, "Add Successful",
- f'"{name}" has been added to your address book.')
- else:
- QMessageBox.information(self, "Add Unsuccessful",
- f'Sorry, "{name}" is already in your address book.')
- return
-
- elif self._current_mode == self.EditingMode:
- if self._old_name != name:
- if name not in self.contacts:
- QMessageBox.information(self, "Edit Successful",
- f'"{self.oldName}" has been edited in your '
- 'address book.')
- del self.contacts[self._old_name]
- self.contacts[name] = address
- else:
- QMessageBox.information(self, "Edit Unsuccessful",
- f'Sorry, "{name}" is already in your address book.')
- return
- elif self._old_address != address:
- QMessageBox.information(self, "Edit Successful",
- f'"{name}" has been edited in your address book.')
- self.contacts[name] = address
-
- self.update_interface(self.NavigationMode)
-
- @Slot()
- def cancel(self):
- self._name_line.setText(self._old_name)
- self._address_text.setText(self._old_address)
- self.update_interface(self.NavigationMode)
-
- @Slot()
- def remove_contact(self):
- name = self._name_line.text()
-
- if name in self.contacts:
- button = QMessageBox.question(self, "Confirm Remove",
- f'Are you sure you want to remove "{name}"?',
- QMessageBox.Yes | QMessageBox.No)
-
- if button == QMessageBox.Yes:
- self.previous()
- del self.contacts[name]
-
- QMessageBox.information(self, "Remove Successful",
- f'"{name}" has been removed from your address book.')
-
- self.update_interface(self.NavigationMode)
-
- @Slot()
- def next(self):
- name = self._name_line.text()
- it = iter(self.contacts)
-
- try:
- while True:
- this_name, _ = it.next()
-
- if this_name == name:
- next_name, next_address = it.next()
- break
- except StopIteration:
- next_name, next_address = iter(self.contacts).next()
-
- self._name_line.setText(next_name)
- self._address_text.setText(next_address)
-
- @Slot()
- def previous(self):
- name = self._name_line.text()
-
- prev_name = prev_address = None
- for this_name, this_address in self.contacts:
- if this_name == name:
- break
-
- prev_name = this_name
- prev_address = this_address
- else:
- self._name_line.clear()
- self._address_text.clear()
- return
-
- if prev_name is None:
- for prev_name, prev_address in self.contacts:
- pass
-
- self._name_line.setText(prev_name)
- self._address_text.setText(prev_address)
-
- def find_contact(self):
- self.dialog.show()
-
- if self.dialog.exec() == QDialog.Accepted:
- contact_name = self.dialog.get_find_text()
-
- if contact_name in self.contacts:
- self._name_line.setText(contact_name)
- self._address_text.setText(self.contacts[contact_name])
- else:
- QMessageBox.information(self, "Contact Not Found",
- f'Sorry, "{contact_name}" is not in your address book.')
- return
-
- self.update_interface(self.NavigationMode)
-
- def update_interface(self, mode):
- self._current_mode = mode
-
- if self._current_mode in (self.AddingMode, self.EditingMode):
- self._name_line.setReadOnly(False)
- self._name_line.setFocus(Qt.FocusReason.OtherFocusReason)
- self._address_text.setReadOnly(False)
-
- self._add_button.setEnabled(False)
- self._edit_button.setEnabled(False)
- self._remove_button.setEnabled(False)
-
- self._next_button.setEnabled(False)
- self._previous_button.setEnabled(False)
-
- self._submit_button.show()
- self._cancel_button.show()
-
- self._load_button.setEnabled(False)
- self._save_button.setEnabled(False)
- self._export_button.setEnabled(False)
-
- elif self._current_mode == self.NavigationMode:
- if not self.contacts:
- self._name_line.clear()
- self._address_text.clear()
-
- self._name_line.setReadOnly(True)
- self._address_text.setReadOnly(True)
- self._add_button.setEnabled(True)
-
- number = len(self.contacts)
- self._edit_button.setEnabled(number >= 1)
- self._remove_button.setEnabled(number >= 1)
- self._find_button.setEnabled(number > 2)
- self._next_button.setEnabled(number > 1)
- self._previous_button.setEnabled(number > 1)
-
- self._submit_button.hide()
- self._cancel_button.hide()
-
- self._export_button.setEnabled(number >= 1)
-
- self._load_button.setEnabled(True)
- self._save_button.setEnabled(number >= 1)
-
- def save_to_file(self):
- fileName, _ = QFileDialog.getSaveFileName(self,
- "Save Address Book", '',
- "Address Book (*.abk);;All Files (*)")
-
- if not fileName:
- return
-
- try:
- out_file = open(str(fileName), 'wb')
- except IOError:
- QMessageBox.information(self, "Unable to open file",
- f'There was an error opening "{fileName}"')
- return
-
- pickle.dump(self.contacts, out_file)
- out_file.close()
-
- def load_from_file(self):
- fileName, _ = QFileDialog.getOpenFileName(self,
- "Open Address Book", '',
- "Address Book (*.abk);;All Files (*)")
-
- if not fileName:
- return
-
- try:
- in_file = open(str(fileName), 'rb')
- except IOError:
- QMessageBox.information(self, "Unable to open file",
- f'There was an error opening "{fileName}"')
- return
-
- self.contacts = pickle.load(in_file)
- in_file.close()
-
- if len(self.contacts) == 0:
- QMessageBox.information(self, "No contacts in file",
- "The file you are attempting to open contains no contacts.")
- else:
- for name, address in self.contacts:
- self._name_line.setText(name)
- self._address_text.setText(address)
-
- self.update_interface(self.NavigationMode)
-
- def export_as_vcard(self):
- name = str(self._name_line.text())
- address = self._address_text.toPlainText()
-
- name_list = name.split()
-
- if len(name_list) > 1:
- first_name = name_list[0]
- last_name = name_list[-1]
- else:
- first_name = name
- last_name = ''
-
- file_name = QFileDialog.getSaveFileName(self, "Export Contact",
- '', "vCard Files (*.vcf);;All Files (*)")[0]
-
- if not file_name:
- return
-
- out_file = QFile(file_name)
-
- if not out_file.open(QIODevice.OpenModeFlag.WriteOnly):
- QMessageBox.information(self, "Unable to open file", out_file.errorString())
- return
-
- out_s = QTextStream(out_file)
-
- out_s << 'BEGIN:VCARD' << '\n'
- out_s << 'VERSION:2.1' << '\n'
- out_s << 'N:' << last_name << ';' << first_name << '\n'
- out_s << 'FN:' << ' '.join(name_list) << '\n'
-
- address.replace(';', '\\;')
- address.replace('\n', ';')
- address.replace(',', ' ')
-
- out_s << 'ADR;HOME:;' << address << '\n'
- out_s << 'END:VCARD' << '\n'
-
- QMessageBox.information(self, "Export Successful",
- f'"{name}" has been exported as a vCard.')
-
-
-class FindDialog(QDialog):
- def __init__(self, parent=None):
- super().__init__(parent)
-
- find_label = QLabel("Enter the name of a contact:")
- self._line_edit = QLineEdit()
-
- self._find_button = QPushButton("&Find")
- self._find_text = ''
-
- layout = QHBoxLayout()
- layout.addWidget(find_label)
- layout.addWidget(self._line_edit)
- layout.addWidget(self._find_button)
-
- self.setLayout(layout)
- self.setWindowTitle("Find a Contact")
-
- self._find_button.clicked.connect(self.find_clicked)
- self._find_button.clicked.connect(self.accept)
-
- def find_clicked(self):
- text = self._line_edit.text()
-
- if not text:
- QMessageBox.information(self, "Empty Field", "Please enter a name.")
- return
-
- self._find_text = text
- self._line_edit.clear()
- self.hide()
-
- def get_find_text(self):
- return self._find_text
-
-
-if __name__ == '__main__':
- app = QApplication(sys.argv)
-
- address_book = AddressBook()
- address_book.show()
-
- sys.exit(app.exec())
import math
from PySide6.QtCore import QPoint, QRect, QTimer, Qt, Signal, Slot, qWarning
-from PySide6.QtGui import QColor, QFont, QPainter, QPalette, QRegion
+from PySide6.QtGui import QColor, QFont, QPainter, QPainterStateGuard, QPalette, QRegion
from PySide6.QtWidgets import (QApplication, QGridLayout, QHBoxLayout,
QLCDNumber, QPushButton, QSlider,
QVBoxLayout, QWidget)
painter.setPen(Qt.PenStyle.NoPen)
painter.setBrush(Qt.GlobalColor.blue)
- painter.save()
- painter.translate(0, self.height())
- painter.drawPie(QRect(-35, -35, 70, 70), 0, 90 * 16)
- painter.rotate(-self._current_angle)
- painter.drawRect(CannonField.barrel_rect)
- painter.restore()
+ with QPainterStateGuard(painter):
+ painter.translate(0, self.height())
+ painter.drawPie(QRect(-35, -35, 70, 70), 0, 90 * 16)
+ painter.rotate(-self._current_angle)
+ painter.drawRect(CannonField.barrel_rect)
def cannon_rect(self):
result = QRect(0, 0, 50, 50)
import random
from PySide6.QtCore import QPoint, QRect, QTime, QTimer, Qt, Signal, Slot, qWarning
-from PySide6.QtGui import QColor, QFont, QPainter, QPalette, QRegion
+from PySide6.QtGui import QColor, QFont, QPainter, QPainterStateGuard, QPalette, QRegion
from PySide6.QtWidgets import (QApplication, QGridLayout, QHBoxLayout,
QLabel, QLCDNumber, QPushButton, QSlider,
QVBoxLayout, QWidget)
painter.setPen(Qt.PenStyle.NoPen)
painter.setBrush(Qt.GlobalColor.blue)
- painter.save()
- painter.translate(0, self.height())
- painter.drawPie(QRect(-35, -35, 70, 70), 0, 90 * 16)
- painter.rotate(-self._current_angle)
- painter.drawRect(CannonField.barrel_rect)
- painter.restore()
+ with QPainterStateGuard(painter):
+ painter.translate(0, self.height())
+ painter.drawPie(QRect(-35, -35, 70, 70), 0, 90 * 16)
+ painter.rotate(-self._current_angle)
+ painter.drawRect(CannonField.barrel_rect)
def cannon_rect(self):
result = QRect(0, 0, 50, 50)
from PySide6.QtCore import (QPoint, QRect, QTime, QTimer, Qt,
Signal, Slot, qWarning)
-from PySide6.QtGui import QColor, QFont, QPainter, QPalette, QRegion
+from PySide6.QtGui import QColor, QFont, QPainter, QPainterStateGuard, QPalette, QRegion
from PySide6.QtWidgets import (QApplication, QGridLayout, QHBoxLayout, QLabel,
QLCDNumber, QPushButton, QSizePolicy, QSlider,
QVBoxLayout, QWidget)
painter.setPen(Qt.PenStyle.NoPen)
painter.setBrush(Qt.GlobalColor.blue)
- painter.save()
- painter.translate(0, self.height())
- painter.drawPie(QRect(-35, -35, 70, 70), 0, 90 * 16)
- painter.rotate(-self._current_angle)
- painter.drawRect(CannonField.barrel_rect)
- painter.restore()
+ with QPainterStateGuard(painter):
+ painter.translate(0, self.height())
+ painter.drawPie(QRect(-35, -35, 70, 70), 0, 90 * 16)
+ painter.rotate(-self._current_angle)
+ painter.drawRect(CannonField.barrel_rect)
def cannon_rect(self):
result = QRect(0, 0, 50, 50)
from PySide6.QtCore import (QPoint, QRect, QTime, QTimer, QSize, Qt,
Signal, Slot, qWarning)
-from PySide6.QtGui import (QColor, QFont, QKeySequence, QPainter, QPalette,
- QShortcut, QRegion, QTransform)
+from PySide6.QtGui import (QColor, QFont, QKeySequence, QPainter, QPainterStateGuard,
+ QPalette, QShortcut, QRegion, QTransform)
from PySide6.QtWidgets import (QApplication, QFrame, QGridLayout, QHBoxLayout,
QLabel, QLCDNumber, QPushButton, QSizePolicy,
QSlider, QVBoxLayout, QWidget)
painter.setPen(Qt.PenStyle.NoPen)
painter.setBrush(Qt.GlobalColor.blue)
- painter.save()
- painter.translate(0, self.height())
- painter.drawPie(QRect(-35, -35, 70, 70), 0, 90 * 16)
- painter.rotate(-self._current_angle)
- painter.drawRect(CannonField.barrel_rect)
- painter.restore()
+ with QPainterStateGuard(painter):
+ painter.translate(0, self.height())
+ painter.drawPie(QRect(-35, -35, 70, 70), 0, 90 * 16)
+ painter.rotate(-self._current_angle)
+ painter.drawRect(CannonField.barrel_rect)
def cannon_rect(self):
result = QRect(0, 0, 50, 50)
import sys
-from PySide6.QtCore import QDir, QFile, Qt, QTextStream
+from PySide6.QtCore import QDir, QFile, QObject, Qt, QTextStream
from PySide6.QtGui import QAction, QIcon, QKeySequence
from PySide6.QtWidgets import (QApplication, QFileDialog, QHeaderView,
QMainWindow, QMessageBox, QStyle, QTreeWidget,
def __init__(self, parent=None):
super().__init__(parent)
+ self._update_conn_id = None
self.header().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
self.setHeaderLabels(("Title", "Location"))
self.clear()
# It might not be connected.
- try:
- self.itemChanged.disconnect(self.update_dom_element)
- except RuntimeError:
- pass
+ if self._update_conn_id:
+ QObject.disconnect(self._update_conn_id)
child = root.firstChildElement('folder')
while not child.isNull():
self.parse_folder_element(child)
child = child.nextSiblingElement('folder')
- self.itemChanged.connect(self.update_dom_element)
+ self._update_conn_id = self.itemChanged.connect(self.update_dom_element)
return True
-pip>=24.2
-setuptools==72.1.0
+pip>=25
+setuptools==78.1.0
importlib_metadata>=6
importlib_resources>=5.10.2
packaging>=24
# Build dependencies
-setuptools==72.1.0
-packaging==24.1
-build==1.2.1
+setuptools==78.1.0
+packaging==24.2
+build==1.2.2.post1
wheel==0.43.0
distro==1.9.0; sys_platform == 'linux'
patchelf==0.17.2; sys_platform == 'linux'
# 2.0.2 is the last version that supports Python 3.9
numpy<=2.0.2; python_version <= '3.9'
numpy==2.1.3; python_version > '3.9'
-mypy[faster-cache]>=1.14.0
+mypy>=1.15.0 # note: 3.13 with disable-gil is not compiled yet
deployment platform etc.
Note: This file is used by both pyside6-deploy and pyside6-android-deploy
-
"""
import sys
def main(main_file: Path = None, name: str = None, config_file: Path = None, init: bool = False,
loglevel=logging.WARNING, dry_run: bool = False, keep_deployment_files: bool = False,
force: bool = False, extra_ignore_dirs: str = None, extra_modules_grouped: str = None,
- mode: str = None):
+ mode: str = None) -> str | None:
+ """
+ Entry point for pyside6-deploy command.
+
+ :return: If successful, the Nuitka command that was executed. None otherwise.
+ """
logging.basicConfig(level=loglevel)
- # in case pyside6-deploy is run from a completely different location than the project
- # directory
+ # In case pyside6-deploy is run from a completely different location than the project directory
if main_file and main_file.exists():
config_file = main_file.parent / "pysidedeploy.spec"
if config_file and not config_file.exists() and not main_file.exists():
raise RuntimeError(dedent("""
Directory does not contain main.py file.
- Please specify the main python entrypoint file or the config file.
- Run "pyside6-deploy desktop --help" to see info about cli options.
+ Please specify the main Python entry point file or the pysidedeploy.spec config file.
+ Run "pyside6-deploy --help" to see info about CLI options.
pyside6-deploy exiting..."""))
- # Nuitka command to run
- command_str = None
- config = None
logging.info("[DEPLOY] Start")
if extra_ignore_dirs:
if config_file_exists:
logging.info(f"[DEPLOY] Using existing config file {config_file}")
else:
- config_file = create_config_file(main_file=main_file, dry_run=dry_run, )
+ config_file = create_config_file(main_file=main_file, dry_run=dry_run)
config = DesktopConfig(config_file=config_file, source_file=main_file, python_exe=python.exe,
dry_run=dry_run, existing_config_file=config_file_exists,
f"{[str(qml_file) for qml_file in config.qml_files]}")
if init:
- # config file created above. Exiting.
+ # Config file created above. Exiting.
logging.info(f"[DEPLOY]: Config file {config.config_file} created")
return
print("[DEPLOY] QtSql Application is not supported on macOS with pyside6-deploy")
return
+ command_str = None
try:
- # create executable
+ # Run the Nuitka command to create the executable
if not dry_run:
logging.info("[DEPLOY] Deploying application")
dry_run=dry_run,
permissions=config.permissions,
mode=config.mode)
+ if not dry_run:
+ logging.info("[DEPLOY] Successfully deployed application")
except Exception:
print(f"[DEPLOY] Exception occurred: {traceback.format_exc()}")
finally:
# the tag number does not matter much since we update the sdk later
DEFAULT_SDK_TAG = 6514223
-ANDROID_NDK_VERSION = "26b"
-ANDROID_NDK_VERSION_NUMBER_SUFFIX = "10909125"
+ANDROID_NDK_VERSION = "27c"
+ANDROID_NDK_VERSION_NUMBER_SUFFIX = "12479018"
def run_command(command: list[str], cwd: str | None = None, ignore_fail: bool = False,
print("Unpacking Android Ndk")
if sys.platform == "darwin":
- extract_dmg(file=(ndk_path
- / f"android-ndk-r{ANDROID_NDK_VERSION}-{sys.platform}.{ndk_extension}"),
- destination=ndk_path)
- ndk_version_path = (ndk_version_path
- / (f"AndroidNDK{ANDROID_NDK_VERSION_NUMBER_SUFFIX}.app"
- "/Contents/NDK"))
+ extract_dmg(file=ndk_zip_path, destination=ndk_path)
else:
- extract_zip(file=(ndk_path
- / f"android-ndk-r{ANDROID_NDK_VERSION}-{sys.platform}.{ndk_extension}"),
- destination=ndk_path)
+ extract_zip(file=ndk_zip_path, destination=ndk_path)
except Exception as e:
print(f"Error occurred while downloading and unpacking Android NDK: {e}")
if ndk_path.exists():
from pathlib import Path
from enum import Enum
-from project_lib import ProjectData, DesignStudioProject
+from project_lib import ProjectData, DesignStudioProject, resolve_valid_project_file
from . import (DEFAULT_APP_ICON, DEFAULT_IGNORE_DIRS, find_pyside_modules,
find_permission_categories, QtDependencyReader, run_qmlimportscanner)
self.extra_ignore_dirs = extra_ignore_dirs
self._dry_run = dry_run
self.qml_modules = set()
- # set source_file
+
self.source_file = Path(
- self.set_or_fetch(config_property_val=source_file, config_property_key="input_file")
+ self.set_or_fetch(property_value=source_file, property_key="input_file")
).resolve()
- # set python path
self.python_path = Path(
self.set_or_fetch(
- config_property_val=python_exe,
- config_property_key="python_path",
- config_property_group="python",
+ property_value=python_exe,
+ property_key="python_path",
+ property_group="python",
)
)
- # set application name
- self.title = self.set_or_fetch(config_property_val=name, config_property_key="title")
+ self.title = self.set_or_fetch(property_value=name, property_key="title")
- # set application icon
config_icon = self.get_value("app", "icon")
if config_icon:
self._icon = str(Path(config_icon).resolve())
self.modules = []
- def set_or_fetch(self, config_property_val, config_property_key, config_property_group="app"):
+ def set_or_fetch(self, property_value, property_key, property_group="app") -> str:
"""
- Set the configuration value if provided, otherwise fetch the existing value.
- Raise an exception if neither is available.
+ If a new property value is provided, store it in the config file
+ Otherwise return the existing value in the config file.
+ Raise an exception if neither are available.
- :param value: The value to set if provided.
- :param key: The configuration key.
- :param group: The configuration group (default is "app").
+ :param property_value: The value to set if provided.
+ :param property_key: The configuration key.
+ :param property_group: The configuration group (default is "app").
:return: The configuration value.
:raises RuntimeError: If no value is provided and no existing value is found.
"""
- existing_value = self.get_value(config_property_group, config_property_key)
+ existing_value = self.get_value(property_group, property_key)
- if config_property_val:
- self.set_value(config_property_group, config_property_key, str(config_property_val))
- return config_property_val
- elif existing_value:
+ if property_value:
+ self.set_value(property_group, property_key, str(property_value))
+ return property_value
+ if existing_value:
return existing_value
- else:
- raise RuntimeError(
- f"[DEPLOY] No value for {config_property_key} specified in config file or as cli"
- " option"
- )
+
+ raise RuntimeError(
+ f"[DEPLOY] No value for {property_key} specified in config file or as cli option"
+ )
@property
- def dry_run(self):
+ def dry_run(self) -> bool:
return self._dry_run
@property
- def generated_files_path(self):
+ def generated_files_path(self) -> Path:
return self._generated_files_path
@property
- def qml_files(self):
+ def qml_files(self) -> list[Path]:
return self._qml_files
@qml_files.setter
- def qml_files(self, qml_files):
+ def qml_files(self, qml_files: list[Path]):
self._qml_files = qml_files
qml_files = [str(file.absolute().relative_to(self.project_dir.absolute()))
if file.absolute().is_relative_to(self.project_dir) else str(file.absolute())
for file in self.qml_files]
+ qml_files.sort()
self.set_value("qt", "qml_files", ",".join(qml_files))
@property
- def project_dir(self):
+ def project_dir(self) -> Path:
return self._project_dir
@project_dir.setter
- def project_dir(self, project_dir):
+ def project_dir(self, project_dir: Path) -> None:
+ rel_path = (
+ project_dir.relative_to(self.config_file.parent)
+ if project_dir.is_relative_to(self.config_file.parent)
+ else project_dir
+ )
self._project_dir = project_dir
- self.set_value("app", "project_dir", str(project_dir))
+ self.set_value("app", "project_dir", str(rel_path))
@property
- def project_file(self):
+ def project_file(self) -> Path:
return self._project_file
@project_file.setter
- def project_file(self, project_file):
+ def project_file(self, project_file: Path):
self._project_file = project_file
self.set_value("app", "project_file", str(project_file.relative_to(self.project_dir)))
@property
- def title(self):
+ def title(self) -> str:
return self._title
@title.setter
- def title(self, title):
+ def title(self, title: str):
self._title = title
@property
- def icon(self):
+ def icon(self) -> str:
return self._icon
@icon.setter
- def icon(self, icon):
+ def icon(self, icon: str):
self._icon = icon
self.set_value("app", "icon", icon)
@property
- def source_file(self):
+ def source_file(self) -> Path:
return self._source_file
@source_file.setter
- def source_file(self, source_file: Path):
+ def source_file(self, source_file: Path) -> None:
+ rel_path = (
+ source_file.relative_to(self.config_file.parent)
+ if source_file.is_relative_to(self.config_file.parent)
+ else source_file
+ )
self._source_file = source_file
- self.set_value("app", "input_file", str(source_file))
+ self.set_value("app", "input_file", str(rel_path))
@property
- def python_path(self):
+ def python_path(self) -> Path:
return self._python_path
@python_path.setter
self._python_path = python_path
@property
- def extra_args(self):
+ def extra_args(self) -> str:
return self.get_value("nuitka", "extra_args")
@extra_args.setter
- def extra_args(self, extra_args):
+ def extra_args(self, extra_args: str):
self.set_value("nuitka", "extra_args", extra_args)
@property
- def excluded_qml_plugins(self):
+ def excluded_qml_plugins(self) -> list[str]:
return self._excluded_qml_plugins
@excluded_qml_plugins.setter
- def excluded_qml_plugins(self, excluded_qml_plugins):
+ def excluded_qml_plugins(self, excluded_qml_plugins: list[str]):
self._excluded_qml_plugins = excluded_qml_plugins
if excluded_qml_plugins: # check required for Android
+ excluded_qml_plugins.sort()
self.set_value("qt", "excluded_qml_plugins", ",".join(excluded_qml_plugins))
@property
- def exe_dir(self):
+ def exe_dir(self) -> Path:
return self._exe_dir
@exe_dir.setter
self.set_value("app", "exec_directory", str(exe_dir))
@property
- def modules(self):
+ def modules(self) -> list[str]:
return self._modules
@modules.setter
- def modules(self, modules):
+ def modules(self, modules: list[str]):
self._modules = modules
+ modules.sort()
self.set_value("qt", "modules", ",".join(modules))
def _find_qml_files(self):
field qml_files is empty in the config_file
"""
- qml_files = []
if self.project_data:
qml_files = [(self.project_dir / str(qml_file)) for qml_file in
self.project_data.qml_files]
if DesignStudioProject.is_ds_project(self.source_file):
return DesignStudioProject(self.source_file).project_dir
- # there is no other way to find the project_dir than assume it is the parent directory
+ # There is no other way to find the project_dir than assume it is the parent directory
# of source_file
return self.source_file.parent
else:
pyproject_location = self.project_dir
- files = list(pyproject_location.glob("*.pyproject"))
- if not files:
- logging.info("[DEPLOY] No .pyproject file found. Project file not set")
- return None
- if len(files) > 1:
- warnings.warn("DEPLOY: More that one .pyproject files found. Project file not set")
- return None
-
- return files[0]
+ try:
+ return resolve_valid_project_file(pyproject_location)
+ except ValueError as e:
+ logging.warning(f"[DEPLOY] Unable to resolve a valid project file. Proceeding without a"
+ f" project file. Details:\n{e}.")
+ return None
def _find_excluded_qml_plugins(self) -> list[str] | None:
if not self.qml_files and not DesignStudioProject.is_ds_project(self.source_file):
f"the resources manually using pyside6-rcc")
@property
- def qt_plugins(self):
+ def qt_plugins(self) -> list[str]:
return self._qt_plugins
@qt_plugins.setter
- def qt_plugins(self, qt_plugins):
+ def qt_plugins(self, qt_plugins: list[str]):
self._qt_plugins = qt_plugins
+ qt_plugins.sort()
self.set_value("qt", "plugins", ",".join(qt_plugins))
@property
- def permissions(self):
+ def permissions(self) -> list[str]:
return self._permissions
@permissions.setter
- def permissions(self, permissions):
+ def permissions(self, permissions: list[str]):
self._permissions = permissions
+ permissions.sort()
self.set_value("nuitka", "macos.permissions", ",".join(permissions))
@property
- def mode(self):
+ def mode(self) -> NuitkaMode:
return self._mode
@mode.setter
logging.info(f"[DEPLOY] Usage descriptions for the {perm_categories_str} will be added to "
"the Info.plist file of the macOS application bundle")
- # handling permissions
+ # Handling permissions
for perm_category in perm_categories:
if perm_category in PERMISSION_MAP:
permissions.append(PERMISSION_MAP[perm_category])
# Title of your application
title = pyside_app_demo
-# Project Directory. The general assumption is that project_dir is the parent directory
-# of input_file
+# Project root directory. Default: The parent directory of input_file
project_dir =
-# Source file path
+# Source file entry point path. Default: main.py
input_file =
# Directory where the executable output is generated
exec_directory =
-# Path to .pyproject project file
+# Path to the project file relative to project_dir
project_file =
# Application icon
# Python path
python_path =
-# python packages to install
-packages = Nuitka==2.5.1
+# Python packages to install
+packages = Nuitka==2.7.11
-# buildozer: for deploying Android application
+# Buildozer: for deploying Android application
android_packages = buildozer==1.5.0,cython==0.29.33
[qt]
-# Comma separated path to QML files required
-# normally all the QML files required by the project are added automatically
+# Paths to required QML files. Comma separated
+# Normally all the QML files required by the project are added automatically
+# Design Studio projects include the QML files using Qt resources
qml_files =
-# excluded qml plugin binaries
+# Excluded qml plugin binaries
excluded_qml_plugins =
# Qt modules used. Comma separated
modules =
-# Qt plugins used by the application. Only relevant for desktop deployment. For Qt plugins used
-# in Android application see [android][plugins]
+# Qt plugins used by the application. Only relevant for desktop deployment
+# For Qt plugins used in Android application see [android][plugins]
plugins =
[android]
-# path to PySide wheel
+# Path to PySide wheel
wheel_pyside =
-# path to Shiboken wheel
+# Path to Shiboken wheel
wheel_shiboken =
-# plugins to be copied to libs folder of the packaged application. Comma separated
+# Plugins to be copied to libs folder of the packaged application. Comma separated
plugins =
[nuitka]
-# usage description for permissions requested by the app as found in the Info.plist file
-# of the app bundle
+# Usage description for permissions requested by the app as found in the Info.plist file
+# of the app bundle. Comma separated
# eg: NSCameraUsageDescription:CameraAccess
macos.permissions =
-# mode of using Nuitka. Accepts standalone or onefile. Default is onefile.
+# Mode of using Nuitka. Accepts standalone or onefile. Default: onefile
mode = onefile
-# (str) specify any extra nuitka arguments
+# Specify any extra nuitka arguments
# eg: extra_args = --show-modules --follow-stdlib
extra_args = --quiet --noinclude-qt-translations
[buildozer]
-# build mode
-# possible options: [release, debug]
-# release creates an aab, while debug creates an apk
+# Build mode
+# Possible values: [release, debug]
+# Release creates a .aab, while debug creates a .apk
mode = debug
-# contrains path to PySide6 and shiboken6 recipe dir
+# Path to PySide6 and shiboken6 recipe dir
recipe_dir =
-# path to extra Qt Android jars to be loaded by the application
+# Path to extra Qt Android .jar files to be loaded by the application
jars_dir =
-# if empty uses default ndk path downloaded by buildozer
+# If empty, uses default NDK path downloaded by buildozer
ndk_path =
-# if empty uses default sdk path downloaded by buildozer
+# If empty, uses default SDK path downloaded by buildozer
sdk_path =
-# other libraries to be loaded. Comma separated.
-# loaded at app startup
+# Other libraries to be loaded at app startup. Comma separated.
local_libs =
-# architecture of deployed platform
-# possible values: ["aarch64", "armv7a", "i686", "x86_64"]
+# Architecture of deployed platform
+# Possible values: ["aarch64", "armv7a", "i686", "x86_64"]
arch =
qrc_candidates = project_data.qrc_files
def add_uic_qrc_candidates(candidates, candidate_type):
- possible_py_candidates = [(file.parent / f"{candidate_type}_{file.stem}.py")
- for file in candidates
- if (file.parent / f"{candidate_type}_{file.stem}.py").exists()
- ]
-
- if len(possible_py_candidates) != len(candidates):
- warnings.warn(f"[DEPLOY] The number of {candidate_type} files and their "
- "corresponding Python files don't match.",
- category=RuntimeWarning)
+ possible_py_candidates = []
+ missing_files = []
+ for file in candidates:
+ py_file = file.parent / f"{candidate_type}_{file.stem}.py"
+ if py_file.exists():
+ possible_py_candidates.append(py_file)
+ else:
+ missing_files.append((str(file), str(py_file)))
+
+ if missing_files:
+ missing_details = "\n".join(
+ f"{candidate_type.upper()} file: {src} -> Missing Python file: {dst}"
+ for src, dst in missing_files
+ )
+ warnings.warn(
+ f"[DEPLOY] The following {candidate_type} files do not have corresponding "
+ f"Python files:\n {missing_details}",
+ category=RuntimeWarning
+ )
py_candidates.extend(possible_py_candidates)
add_uic_qrc_candidates(ui_candidates, "ui")
if qrc_candidates:
- add_uic_qrc_candidates(qrc_candidates, "qrc")
+ add_uic_qrc_candidates(qrc_candidates, "rc")
return py_candidates
return node.value.id, node.attr
-def _name(node: ast.Name | ast.Attribute) -> str:
+def _name(node: ast.Name | ast.Attribute | ast.Constant) -> str:
"""Return the name of something that is either an attribute or a name,
such as base classes or call.func"""
+ if isinstance(node, ast.Constant):
+ return str(node.value)
if isinstance(node, ast.Attribute):
qualifier, name = _attribute(node)
return f"{qualifier}.{node.attr}"
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
from __future__ import annotations
-"""
-Builds a '.pyproject' file
-
-Builds Qt Designer forms, resource files and QML type files
-
-Deploys the application by creating an executable for the corresponding platform
-
-For each entry in a '.pyproject' file:
-- <name>.pyproject: Recurse to handle subproject
-- <name>.qrc : Runs the resource compiler to create a file rc_<name>.py
-- <name>.ui : Runs the user interface compiler to create a file ui_<name>.py
-
-For a Python file declaring a QML module, a directory matching the URI is
-created and populated with .qmltypes and qmldir files for use by code analysis
-tools. Currently, only one QML module consisting of several classes can be
-handled per project file.
-"""
import sys
import os
from pathlib import Path
from argparse import ArgumentParser, RawTextHelpFormatter
-from project_lib import (QmlProjectData, check_qml_decorators, is_python_file,
- QMLDIR_FILE, MOD_CMD, METATYPES_JSON_SUFFIX,
- SHADER_SUFFIXES, TRANSLATION_SUFFIX,
- requires_rebuild, run_command, remove_path,
- ProjectData, resolve_project_file, new_project,
- ProjectType, ClOptions, DesignStudioProject)
-
-MODE_HELP = """build Builds the project
-run Builds the project and runs the first file")
-clean Cleans the build artifacts")
-qmllint Runs the qmllint tool
-deploy Deploys the application
-lupdate Updates translation (.ts) files
-new-ui Creates a new QtWidgets project with a Qt Designer-based main window
-new-widget Creates a new QtWidgets project with a main window
-new-quick Creates a new QtQuick project
+from project_lib import (QmlProjectData, check_qml_decorators, is_python_file, migrate_pyproject,
+ QMLDIR_FILE, MOD_CMD, METATYPES_JSON_SUFFIX, SHADER_SUFFIXES,
+ TRANSLATION_SUFFIX, requires_rebuild, run_command, remove_path,
+ ProjectData, resolve_valid_project_file, new_project, NewProjectTypes,
+ ClOptions, DesignStudioProject)
+
+DESCRIPTION = """
+pyside6-project is a command line tool for creating, building and deploying Qt for Python
+applications. It operates on project files which are also used by Qt Creator.
+
+Official documentation:
+https://doc.qt.io/qtforpython-6/tools/pyside-project.html
"""
+OPERATION_HELP = {
+ "build": "Build the project. Compiles resources, UI files, and QML files if existing and "
+ "necessary.",
+ "run": "Build and run the project.",
+ "clean": "Clean build artifacts and generated files from the project directory.",
+ "qmllint": "Run the qmllint tool on QML files in the project.",
+ "deploy": "Create a deployable package of the application including all dependencies.",
+ "lupdate": "Update translation files (.ts) with new strings from source files.",
+ "migrate-pyproject": "Migrate a *.pyproject file to pyproject.toml format."
+}
+
UIC_CMD = "pyside6-uic"
RCC_CMD = "pyside6-rcc"
LRELEASE_CMD = "pyside6-lrelease"
QSB_CMD = "pyside6-qsb"
DEPLOY_CMD = "pyside6-deploy"
-NEW_PROJECT_TYPES = {"new-quick": ProjectType.QUICK,
- "new-ui": ProjectType.WIDGET_FORM,
- "new-widget": ProjectType.WIDGET}
-
def _sort_sources(files: list[Path]) -> list[Path]:
"""Sort the sources for building, ensure .qrc is last since it might depend
self._regenerate_qmldir()
- def run(self):
+ def run(self) -> int:
"""Runs the project"""
self.build()
cmd = [sys.executable, str(self.project.main_file)]
- run_command(cmd, cwd=self.project.project_file.parent)
+ return run_command(cmd, cwd=self.project.project_file.parent)
def _clean_file(self, source: Path):
"""Clean an artifact."""
cmd_prefix = [LUPDATE_CMD] + [os.fspath(p.relative_to(project_dir)) for p in source_files]
cmd_prefix.append("-ts")
for ts_file in self.project.ts_files:
+ ts_dir = ts_file.parent
+ if not ts_dir.exists():
+ ts_dir.mkdir(parents=True, exist_ok=True)
if requires_rebuild(source_files, ts_file):
cmd = cmd_prefix
- cmd.append(ts_file.name)
+ cmd.append(os.fspath(ts_file))
run_command(cmd, cwd=project_dir)
-def main(mode: str = None, file: str = None, dry_run: bool = False, quiet: bool = False,
- force: bool = False, qml_module: bool = None):
+def main(mode: str = None, dry_run: bool = False, quiet: bool = False, force: bool = False,
+ qml_module: bool = None, project_dir: str = None, project_path: str = None,
+ legacy_pyproject: bool = False):
cl_options = ClOptions(dry_run=dry_run, quiet=quiet, # noqa: F841
force=force, qml_module=qml_module)
- new_project_type = NEW_PROJECT_TYPES.get(mode)
- if new_project_type:
- if not file:
- print(f"{mode} requires a directory name.", file=sys.stderr)
+ if new_project_type := NewProjectTypes.find_by_command(mode):
+ if not project_dir:
+ print(f"Error creating new project: {mode} requires a directory name or path",
+ file=sys.stderr)
+ sys.exit(1)
+
+ project_dir = Path(project_dir)
+ try:
+ project_dir.resolve()
+ project_dir.mkdir(parents=True, exist_ok=True)
+ except (OSError, RuntimeError, ValueError):
+ print("Invalid project name", file=sys.stderr)
sys.exit(1)
- sys.exit(new_project(file, new_project_type))
- project_file = resolve_project_file(file)
- if not project_file:
- print(f"Cannot determine project_file {file}", file=sys.stderr)
+ sys.exit(new_project(project_dir, new_project_type, legacy_pyproject))
+
+ if mode == "migrate-pyproject":
+ sys.exit(migrate_pyproject(project_path))
+
+ try:
+ project_file = resolve_valid_project_file(project_path)
+ except ValueError as e:
+ print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
+
project = Project(project_file)
if mode == "build":
project.build()
elif mode == "run":
- project.run()
+ sys.exit(project.run())
elif mode == "clean":
project.clean()
elif mode == "qmllint":
if __name__ == "__main__":
- parser = ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
+ parser = ArgumentParser(description=DESCRIPTION, formatter_class=RawTextHelpFormatter)
parser.add_argument("--quiet", "-q", action="store_true", help="Quiet")
parser.add_argument("--dry-run", "-n", action="store_true", help="Only print commands")
parser.add_argument("--force", "-f", action="store_true", help="Force rebuild")
parser.add_argument("--qml-module", "-Q", action="store_true",
help="Perform check for QML module")
- mode_choices = ["build", "run", "clean", "qmllint", "deploy", "lupdate"]
- mode_choices.extend(NEW_PROJECT_TYPES.keys())
- parser.add_argument("mode", choices=mode_choices, default="build",
- type=str, help=MODE_HELP)
- # TODO: improve the command structure.
- # "File" argument is not correct when doing new-... project
- parser.add_argument("file", help="Project file", nargs="?", type=str)
+ # Create subparsers for the two different command branches
+ subparsers = parser.add_subparsers(dest='mode', required=True)
+
+ # Add subparser for project creation commands
+ for project_type in NewProjectTypes:
+ new_parser = subparsers.add_parser(project_type.value.command,
+ help=project_type.value.description)
+ new_parser.add_argument(
+ "project_dir", help="Name or location of the new project", nargs="?", type=str)
+
+ new_parser.add_argument(
+ "--legacy-pyproject", action="store_true", help="Create a legacy *.pyproject file")
+
+ # Add subparser for project operation commands
+ for op_mode, op_help in OPERATION_HELP.items():
+ op_parser = subparsers.add_parser(op_mode, help=op_help)
+ op_parser.add_argument("project_path", nargs="?", type=str, help="Path to the project file")
args = parser.parse_args()
- main(args.mode, args.file, args.dry_run, args.quiet, args.force, args.qml_module)
+
+ main(args.mode, args.dry_run, args.quiet, args.force, args.qml_module,
+ getattr(args, "project_dir", None), getattr(args, "project_path", None),
+ getattr(args, "legacy_pyproject", None))
QTPATHS_CMD = "qtpaths6"
MOD_CMD = "pyside6-metaobjectdump"
-PROJECT_FILE_SUFFIX = ".pyproject"
+PYPROJECT_TOML_PATTERN = "pyproject.toml"
+PYPROJECT_JSON_PATTERN = "*.pyproject"
+# Note that the order is important, as the first pattern that matches is used
+PYPROJECT_FILE_PATTERNS = [PYPROJECT_TOML_PATTERN, PYPROJECT_JSON_PATTERN]
QMLDIR_FILE = "qmldir"
QML_IMPORT_NAME = "QML_IMPORT_NAME"
from .utils import (run_command, requires_rebuild, remove_path, package_dir, qtpaths,
- qt_metatype_json_dir, resolve_project_file)
+ qt_metatype_json_dir, resolve_valid_project_file)
from .project_data import (is_python_file, ProjectData, QmlProjectData,
check_qml_decorators)
-from .newproject import new_project, ProjectType
+from .newproject import new_project, NewProjectTypes
from .design_studio_project import DesignStudioProject
+from .pyproject_toml import parse_pyproject_toml, write_pyproject_toml, migrate_pyproject
+from .pyproject_json import parse_pyproject_json
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
from __future__ import annotations
-import json
import os
import sys
+from dataclasses import dataclass
from enum import Enum
from pathlib import Path
-"""New project generation code."""
-
-
-Project = list[tuple[str, str]] # tuple of (filename, contents).
-
-
-class ProjectType(Enum):
- WIDGET_FORM = 1
- WIDGET = 2
- QUICK = 3
+from .pyproject_toml import write_pyproject_toml
+from .pyproject_json import write_pyproject_json
+"""New project generation code."""
_WIDGET_MAIN = """if __name__ == '__main__':
app = QApplication(sys.argv)
sys.exit(app.exec())
"""
-
_WIDGET_IMPORTS = """import sys
from PySide6.QtWidgets import QApplication, QMainWindow
"""
-
_WIDGET_CLASS_DEFINITION = """class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
"""
-
_WIDGET_SETUP_UI_CODE = """ self._ui = Ui_MainWindow()
self._ui.setupUi(self)
"""
-
_MAINWINDOW_FORM = """<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
</ui>
"""
-
_QUICK_FORM = """import QtQuick
import QtQuick.Controls
sys.exit(exit_code)
"""
+NewProjectFiles = list[tuple[str, str]] # tuple of (filename, contents).
+
-def _write_project(directory: Path, files: Project):
- """Write out the project."""
- file_list = []
- for file, contents in files:
- (directory / file).write_text(contents)
- print(f"Wrote {directory.name}{os.sep}{file}.")
- file_list.append(file)
- pyproject = {"files": file_list}
- pyproject_file = f"{directory}.pyproject"
- (directory / pyproject_file).write_text(json.dumps(pyproject))
- print(f"Wrote {directory.name}{os.sep}{pyproject_file}.")
+@dataclass(frozen=True)
+class NewProjectType:
+ command: str
+ description: str
+ files: NewProjectFiles
-def _widget_project() -> Project:
+def _write_project(directory: Path, files: NewProjectFiles, legacy_pyproject: bool):
+ """
+ Create the project files in the specified directory.
+
+ :param directory: The directory to create the project in.
+ :param files: The files that belong to the project to create.
+ """
+ file_names = []
+ for file_name, contents in files:
+ (directory / file_name).write_text(contents)
+ print(f"Wrote {directory.name}{os.sep}{file_name}.")
+ file_names.append(file_name)
+
+ if legacy_pyproject:
+ pyproject_file = directory / f"{directory.name}.pyproject"
+ write_pyproject_json(pyproject_file, file_names)
+ else:
+ pyproject_file = directory / "pyproject.toml"
+ write_pyproject_toml(pyproject_file, directory.name, file_names)
+ print(f"Wrote {pyproject_file}.")
+
+
+def _widget_project() -> NewProjectFiles:
"""Create a (form-less) widgets project."""
main_py = (_WIDGET_IMPORTS + "\n\n" + _WIDGET_CLASS_DEFINITION + "\n\n"
+ _WIDGET_MAIN)
return [("main.py", main_py)]
-def _ui_form_project() -> Project:
+def _ui_form_project() -> NewProjectFiles:
"""Create a Qt Designer .ui form based widgets project."""
main_py = (_WIDGET_IMPORTS
+ "\nfrom ui_mainwindow import Ui_MainWindow\n\n\n"
("mainwindow.ui", _MAINWINDOW_FORM)]
-def _qml_project() -> Project:
+def _qml_project() -> NewProjectFiles:
"""Create a QML project."""
return [("main.py", _QUICK_MAIN),
("main.qml", _QUICK_FORM)]
-def new_project(directory_s: str,
- project_type: ProjectType = ProjectType.WIDGET_FORM) -> int:
- directory = Path(directory_s)
- if directory.exists():
- print(f"{directory_s} already exists.", file=sys.stderr)
- return -1
- directory.mkdir(parents=True)
+class NewProjectTypes(Enum):
+ QUICK = NewProjectType("new-quick", "Create a new Qt Quick project", _qml_project())
+ WIDGET_FORM = NewProjectType("new-ui", "Create a new Qt Widgets Form project",
+ _ui_form_project())
+ WIDGET = NewProjectType("new-widget", "Create a new Qt Widgets project", _widget_project())
- if project_type == ProjectType.WIDGET_FORM:
- project = _ui_form_project()
- elif project_type == ProjectType.QUICK:
- project = _qml_project()
- else:
- project = _widget_project()
- _write_project(directory, project)
- if project_type == ProjectType.WIDGET_FORM:
- print(f'Run "pyside6-project build {directory_s}" to build the project')
- print(f'Run "python {directory.name}{os.sep}main.py" to run the project')
+ @staticmethod
+ def find_by_command(command: str) -> NewProjectType | None:
+ return next((pt.value for pt in NewProjectTypes if pt.value.command == command), None)
+
+
+def new_project(
+ project_dir: Path, project_type: NewProjectType, legacy_pyproject: bool
+) -> int:
+ """
+ Create a new project at the specified project_dir directory.
+
+ :param project_dir: The directory path to create the project. If existing, must be empty.
+ :param project_type: The Qt type of project to create (Qt Widgets, Qt Quick, etc.)
+
+ :return: 0 if the project was created successfully, otherwise 1.
+ """
+ if any(project_dir.iterdir()):
+ print(f"Can not create project at {project_dir}: directory is not empty.", file=sys.stderr)
+ return 1
+ project_dir.mkdir(parents=True, exist_ok=True)
+
+ try:
+ _write_project(project_dir, project_type.files, legacy_pyproject)
+ except Exception as e:
+ print(f"Error creating project file: {str(e)}", file=sys.stderr)
+ return 1
+
+ if project_type == NewProjectTypes.WIDGET_FORM:
+ print(f'Run "pyside6-project build {project_dir}" to build the project')
+ print(f'Run "pyside6-project run {project_dir / "main.py"}" to run the project')
return 0
import subprocess
import sys
from pathlib import Path
-from . import (METATYPES_JSON_SUFFIX, PROJECT_FILE_SUFFIX, TRANSLATION_SUFFIX,
- qt_metatype_json_dir, MOD_CMD, QML_IMPORT_MAJOR_VERSION,
- QML_IMPORT_MINOR_VERSION, QML_IMPORT_NAME, QT_MODULES)
+from . import (METATYPES_JSON_SUFFIX, PYPROJECT_JSON_PATTERN, PYPROJECT_TOML_PATTERN,
+ PYPROJECT_FILE_PATTERNS, TRANSLATION_SUFFIX, qt_metatype_json_dir, MOD_CMD,
+ QML_IMPORT_MAJOR_VERSION, QML_IMPORT_MINOR_VERSION, QML_IMPORT_NAME, QT_MODULES)
+from .pyproject_toml import parse_pyproject_toml
+from .pyproject_json import parse_pyproject_json
def is_python_file(file: Path) -> bool:
class ProjectData:
def __init__(self, project_file: Path) -> None:
- """Parse the project."""
+ """Parse the project file."""
self._project_file = project_file.resolve()
self._sub_projects_files: list[Path] = []
# ts files
self._ts_files: list[Path] = []
- with project_file.open("r") as pyf:
- pyproject = json.load(pyf)
- for f in pyproject["files"]:
- file = Path(project_file.parent / f)
- if file.suffix == PROJECT_FILE_SUFFIX:
- self._sub_projects_files.append(file)
- else:
- self._files.append(file)
- if file.suffix == ".qml":
- self._qml_files.append(file)
- elif is_python_file(file):
- if file.stem == "main":
- self.main_file = file
- self._python_files.append(file)
- elif file.suffix == ".ui":
- self._ui_files.append(file)
- elif file.suffix == ".qrc":
- self._qrc_files.append(file)
- elif file.suffix == TRANSLATION_SUFFIX:
- self._ts_files.append(file)
+ if project_file.match(PYPROJECT_JSON_PATTERN):
+ project_file_data = parse_pyproject_json(project_file)
+ elif project_file.match(PYPROJECT_TOML_PATTERN):
+ project_file_data = parse_pyproject_toml(project_file)
+ else:
+ print(f"Unknown project file format: {project_file}", file=sys.stderr)
+ sys.exit(1)
+
+ if project_file_data.errors:
+ print(f"Invalid project file: {project_file}. Errors found:", file=sys.stderr)
+ for error in project_file_data.errors:
+ print(f"{error}", file=sys.stderr)
+ sys.exit(1)
+
+ for f in project_file_data.files:
+ file = Path(project_file.parent / f)
+ if any(file.match(pattern) for pattern in PYPROJECT_FILE_PATTERNS):
+ self._sub_projects_files.append(file)
+ continue
+
+ self._files.append(file)
+ if file.suffix == ".qml":
+ self._qml_files.append(file)
+ elif is_python_file(file):
+ if file.stem == "main":
+ self.main_file = file
+ self._python_files.append(file)
+ elif file.suffix == ".ui":
+ self._ui_files.append(file)
+ elif file.suffix == ".qrc":
+ self._qrc_files.append(file)
+ elif file.suffix == TRANSLATION_SUFFIX:
+ self._ts_files.append(file)
if not self.main_file:
self._find_main_file()
--- /dev/null
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+import json
+from pathlib import Path
+
+from .pyproject_parse_result import PyProjectParseResult
+
+
+def write_pyproject_json(pyproject_file: Path, project_files: list[str]):
+ """
+ Create or update a *.pyproject file with the specified content.
+
+ :param pyproject_file: The *.pyproject file path to create or update.
+ :param project_files: The relative paths of the files to include in the project.
+ """
+ # The content of the file is fully replaced, so it is not necessary to read and merge any
+ # existing content
+ content = {
+ "files": sorted(project_files),
+ }
+ pyproject_file.write_text(json.dumps(content), encoding="utf-8")
+
+
+def parse_pyproject_json(pyproject_json_file: Path) -> PyProjectParseResult:
+ """
+ Parse a pyproject.json file and return a PyProjectParseResult object.
+ """
+ result = PyProjectParseResult()
+ try:
+ with pyproject_json_file.open("r") as pyf:
+ project_file_data = json.load(pyf)
+ except json.JSONDecodeError as e:
+ result.errors.append(str(e))
+ return result
+ except Exception as e:
+ result.errors.append(str(e))
+ return result
+
+ if not isinstance(project_file_data, dict):
+ result.errors.append("The root element of pyproject.json must be a JSON object")
+ return result
+
+ found_files = project_file_data.get("files")
+ if found_files and not isinstance(found_files, list):
+ result.errors.append("The files element must be a list")
+ return result
+
+ for file in project_file_data.get("files", []):
+ if not isinstance(file, str):
+ result.errors.append(f"Invalid file: {file}")
+ return result
+
+ file_path = Path(file)
+ if not file_path.is_absolute():
+ file_path = (pyproject_json_file.parent / file).resolve()
+ result.files.append(file_path)
+
+ return result
--- /dev/null
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+from dataclasses import dataclass, field
+from pathlib import Path
+
+
+@dataclass
+class PyProjectParseResult:
+ errors: list[str] = field(default_factory=list)
+ files: list[Path] = field(default_factory=list)
--- /dev/null
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+from __future__ import annotations
+
+import sys
+# TODO: Remove this import when Python 3.11 is the minimum supported version
+if sys.version_info >= (3, 11):
+ import tomllib
+from pathlib import Path
+
+from . import PYPROJECT_JSON_PATTERN
+from .pyproject_parse_result import PyProjectParseResult
+from .pyproject_json import parse_pyproject_json
+
+
+def _parse_toml_content(content: str) -> dict:
+ """
+ Parse TOML content for project name and files list only.
+ """
+ result = {"project": {}, "tool": {"pyside6-project": {}}}
+ current_section = None
+
+ for line in content.splitlines():
+ line = line.strip()
+ if not line or line.startswith('#'):
+ continue
+
+ if line == '[project]':
+ current_section = 'project'
+ elif line == '[tool.pyside6-project]':
+ current_section = 'tool.pyside6-project'
+ elif '=' in line and current_section:
+ key, value = [part.strip() for part in line.split('=', 1)]
+
+ # Handle string values - name of the project
+ if value.startswith('"') and value.endswith('"'):
+ value = value[1:-1]
+ # Handle array of strings - files names
+ elif value.startswith('[') and value.endswith(']'):
+ items = value[1:-1].split(',')
+ value = [item.strip().strip('"') for item in items if item.strip()]
+
+ if current_section == 'project':
+ result['project'][key] = value
+ else: # tool.pyside6-project
+ result['tool']['pyside6-project'][key] = value
+
+ return result
+
+
+def _write_toml_content(data: dict) -> str:
+ """
+ Write minimal TOML content with project and tool.pyside6-project sections.
+ """
+ lines = []
+
+ if 'project' in data and data['project']:
+ lines.append('[project]')
+ for key, value in sorted(data['project'].items()):
+ if isinstance(value, str):
+ lines.append(f'{key} = "{value}"')
+
+ if 'tool' in data and 'pyside6-project' in data['tool']:
+ lines.append('\n[tool.pyside6-project]')
+ for key, value in sorted(data['tool']['pyside6-project'].items()):
+ if isinstance(value, list):
+ items = [f'"{item}"' for item in sorted(value)]
+ lines.append(f'{key} = [{", ".join(items)}]')
+ else:
+ lines.append(f'{key} = "{value}"')
+
+ return '\n'.join(lines)
+
+
+def parse_pyproject_toml(pyproject_toml_file: Path) -> PyProjectParseResult:
+ """
+ Parse a pyproject.toml file and return a PyProjectParseResult object.
+ """
+ result = PyProjectParseResult()
+
+ try:
+ content = pyproject_toml_file.read_text(encoding='utf-8')
+ # TODO: Remove the manual parsing when Python 3.11 is the minimum supported version
+ if sys.version_info >= (3, 11):
+ root_table = tomllib.loads(content) # Use tomllib for Python >= 3.11
+ print("Using tomllib for parsing TOML content")
+ else:
+ root_table = _parse_toml_content(content) # Fallback to manual parsing
+ except Exception as e:
+ result.errors.append(str(e))
+ return result
+
+ pyside_table = root_table.get("tool", {}).get("pyside6-project", {})
+ if not pyside_table:
+ result.errors.append("Missing [tool.pyside6-project] table")
+ return result
+
+ files = pyside_table.get("files", [])
+ if not isinstance(files, list):
+ result.errors.append("Missing or invalid files list")
+ return result
+
+ # Convert paths
+ for file in files:
+ if not isinstance(file, str):
+ result.errors.append(f"Invalid file: {file}")
+ return result
+ file_path = Path(file)
+ if not file_path.is_absolute():
+ file_path = (pyproject_toml_file.parent / file).resolve()
+ result.files.append(file_path)
+
+ return result
+
+
+def write_pyproject_toml(pyproject_file: Path, project_name: str, project_files: list[str]):
+ """
+ Create or update a pyproject.toml file with the specified content.
+ """
+ data = {
+ "project": {"name": project_name},
+ "tool": {
+ "pyside6-project": {"files": sorted(project_files)}
+ }
+ }
+
+ try:
+ content = _write_toml_content(data)
+ pyproject_file.write_text(content, encoding='utf-8')
+ except Exception as e:
+ raise ValueError(f"Error writing TOML file: {str(e)}")
+
+
+def migrate_pyproject(pyproject_file: Path | str = None) -> int:
+ """
+ Migrate a project *.pyproject JSON file to the new pyproject.toml format.
+
+ The containing subprojects are migrated recursively.
+
+ :return: 0 if successful, 1 if an error occurred.
+ """
+ project_name = None
+
+ # Transform the user input string into a Path object
+ if isinstance(pyproject_file, str):
+ pyproject_file = Path(pyproject_file)
+
+ if pyproject_file:
+ if not pyproject_file.match(PYPROJECT_JSON_PATTERN):
+ print(f"Cannot migrate non \"{PYPROJECT_JSON_PATTERN}\" file:", file=sys.stderr)
+ print(f"\"{pyproject_file}\"", file=sys.stderr)
+ return 1
+ project_files = [pyproject_file]
+ project_name = pyproject_file.stem
+ else:
+ # Get the existing *.pyproject files in the current directory
+ project_files = list(Path().glob(PYPROJECT_JSON_PATTERN))
+ if not project_files:
+ print(f"No project file found in the current directory: {Path()}", file=sys.stderr)
+ return 1
+ if len(project_files) > 1:
+ print("Multiple pyproject files found in the project folder:")
+ print('\n'.join(str(project_file) for project_file in project_files))
+ response = input("Continue? y/n: ")
+ if response.lower().strip() not in {"yes", "y"}:
+ return 0
+ else:
+ # If there is only one *.pyproject file in the current directory,
+ # use its file name as the project name
+ project_name = project_files[0].stem
+
+ # The project files that will be written to the pyproject.toml file
+ output_files = set()
+ for project_file in project_files:
+ project_data = parse_pyproject_json(project_file)
+ if project_data.errors:
+ print(f"Invalid project file: {project_file}. Errors found:", file=sys.stderr)
+ print('\n'.join(project_data.errors), file=sys.stderr)
+ return 1
+ output_files.update(project_data.files)
+
+ project_folder = project_files[0].parent.resolve()
+ if project_name is None:
+ # If a project name has not resolved, use the name of the parent folder
+ project_name = project_folder.name
+
+ pyproject_toml_file = project_folder / "pyproject.toml"
+ if pyproject_toml_file.exists():
+ already_existing_file = True
+ try:
+ content = pyproject_toml_file.read_text(encoding='utf-8')
+ data = _parse_toml_content(content)
+ except Exception as e:
+ raise ValueError(f"Error parsing TOML: {str(e)}")
+ else:
+ already_existing_file = False
+ data = {"project": {}, "tool": {"pyside6-project": {}}}
+
+ # Update project name if not present
+ if "name" not in data["project"]:
+ data["project"]["name"] = project_name
+
+ # Update files list
+ data["tool"]["pyside6-project"]["files"] = sorted(
+ p.relative_to(project_folder).as_posix() for p in output_files
+ )
+
+ # Generate TOML content
+ toml_content = _write_toml_content(data)
+
+ if already_existing_file:
+ print(f"WARNING: A pyproject.toml file already exists at \"{pyproject_toml_file}\"")
+ print("The file will be updated with the following content:")
+ print(toml_content)
+ response = input("Proceed? [Y/n] ")
+ if response.lower().strip() not in {"yes", "y"}:
+ return 0
+
+ try:
+ pyproject_toml_file.write_text(toml_content)
+ except Exception as e:
+ print(f"Error writing to \"{pyproject_toml_file}\": {str(e)}", file=sys.stderr)
+ return 1
+
+ if not already_existing_file:
+ print(f"Created \"{pyproject_toml_file}\"")
+ else:
+ print(f"Updated \"{pyproject_toml_file}\"")
+
+ # Recursively migrate the subprojects
+ for sub_project_file in filter(lambda f: f.match(PYPROJECT_JSON_PATTERN), output_files):
+ result = migrate_pyproject(sub_project_file)
+ if result != 0:
+ return result
+ return 0
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
from __future__ import annotations
-import sys
import subprocess
-from pathlib import Path
+import sys
import xml.etree.ElementTree as ET
+from pathlib import Path
+
+from . import (QTPATHS_CMD, PYPROJECT_JSON_PATTERN, PYPROJECT_TOML_PATTERN, PYPROJECT_FILE_PATTERNS,
+ ClOptions)
+from .pyproject_toml import parse_pyproject_toml
+from .pyproject_json import parse_pyproject_json
+
-from . import QTPATHS_CMD, PROJECT_FILE_SUFFIX, ClOptions
+def run_command(command: list[str], cwd: str = None, ignore_fail: bool = False) -> int:
+ """
+ Run a command using a subprocess.
+ If dry run is enabled, the command will be printed to stdout instead of being executed.
+ :param command: The command to run including the arguments
+ :param cwd: The working directory to run the command in
+ :param ignore_fail: If True, the current process will not exit if the command fails
-def run_command(command: list[str], cwd: str = None, ignore_fail: bool = False):
- """Run a command observing quiet/dry run"""
+ :return: The exit code of the command
+ """
cloptions = ClOptions()
if not cloptions.quiet or cloptions.dry_run:
print(" ".join(command))
- if not cloptions.dry_run:
- ex = subprocess.call(command, cwd=cwd)
- if ex != 0 and not ignore_fail:
- sys.exit(ex)
+ if cloptions.dry_run:
+ return 0
+
+ ex = subprocess.call(command, cwd=cwd)
+ if ex != 0 and not ignore_fail:
+ sys.exit(ex)
+ return ex
def qrc_file_requires_rebuild(resources_file_path: Path, compiled_resources_path: Path) -> bool:
if source.stat().st_mtime > artifact_mod_time:
return True
# The .qrc file references other files that might have changed
- if source.suffix == '.qrc' and qrc_file_requires_rebuild(source, artifact):
+ if source.suffix == ".qrc" and qrc_file_requires_rebuild(source, artifact):
return True
return False
return _qt_metatype_json_dir
-def resolve_project_file(cmdline: str) -> Path | None:
- """Return the project file from the command line value, either
- from the file argument or directory"""
- project_file = Path(cmdline).resolve() if cmdline else Path.cwd()
- if project_file.is_file():
+def resolve_valid_project_file(
+ project_path_input: str = None, project_file_patterns: list[str] = PYPROJECT_FILE_PATTERNS
+) -> Path:
+ """
+ Find a valid project file given a preferred project file name and a list of project file name
+ patterns for a fallback search.
+
+ If the provided file name is a valid project file, return it. Otherwise, search for a known
+ project file in the current working directory with the given patterns.
+
+ Raises a ValueError if no project file is found, multiple project files are found in the same
+ directory or the provided path is not a valid project file or folder.
+
+ :param project_path_input: The command-line argument specifying a project file or folder path.
+ :param project_file_patterns: The list of project file patterns to search for.
+
+ :return: The resolved project file path
+ """
+ if project_path_input and (project_file := Path(project_path_input).resolve()).is_file():
+ if project_file.match(PYPROJECT_TOML_PATTERN):
+ if bool(parse_pyproject_toml(project_file).errors):
+ raise ValueError(f"Invalid project file: {project_file}")
+ elif project_file.match(PYPROJECT_JSON_PATTERN):
+ pyproject_json_result = parse_pyproject_json(project_file)
+ if errors := '\n'.join(str(e) for e in pyproject_json_result.errors):
+ raise ValueError(f"Invalid project file: {project_file}\n{errors}")
+ else:
+ raise ValueError(f"Unknown project file: {project_file}")
+ return project_file
+
+ project_folder = Path.cwd()
+ if project_path_input:
+ if not Path(project_path_input).resolve().is_dir():
+ raise ValueError(f"Invalid project path: {project_path_input}")
+ project_folder = Path(project_path_input).resolve()
+
+ # Search a project file in the project folder using the provided patterns
+ for pattern in project_file_patterns:
+ if not (matches := list(project_folder.glob(pattern))):
+ # No project files found with the specified pattern
+ continue
+
+ if len(matches) > 1:
+ matched_files = '\n'.join(str(f) for f in matches)
+ raise ValueError(f"Multiple project files found:\n{matched_files}")
+
+ project_file = matches[0]
+
+ if pattern == PYPROJECT_TOML_PATTERN:
+ if parse_pyproject_toml(project_file).errors:
+ # Invalid file, but a .pyproject file may exist
+ # We can not raise an error due to ensuring backward compatibility
+ continue
+ elif pattern == PYPROJECT_JSON_PATTERN:
+ pyproject_json_result = parse_pyproject_json(project_file)
+ if errors := '\n'.join(str(e) for e in pyproject_json_result.errors):
+ raise ValueError(f"Invalid project file: {project_file}\n{errors}")
+
+ # Found a valid project file
return project_file
- if project_file.is_dir():
- for m in project_file.glob(f"*{PROJECT_FILE_SUFFIX}"):
- return m
- return None
+
+ raise ValueError("No project file found in the current directory")
pyside_script_wrapper("metaobjectdump.py")
+def _check_requirements(requirements_file):
+ """Check if all required packages are installed."""
+ missing_packages = []
+ with open(requirements_file, 'r', encoding='UTF-8') as file:
+ for line in file:
+ # versions
+ package = line.strip().split('==')[0]
+ if not importlib.util.find_spec(package):
+ missing_packages.append(line.strip())
+ return missing_packages
+
+
def project():
pyside_script_wrapper("project.py")
file=sys.stderr)
else:
android_requirements_file = Path(__file__).parent / "requirements-android.txt"
- with open(android_requirements_file, 'r', encoding='UTF-8') as file:
- while line := file.readline():
- dependent_package = line.rstrip()
- if not bool(importlib.util.find_spec(dependent_package)):
- command = [sys.executable, "-m", "pip", "install", dependent_package]
- subprocess.run(command)
+ if android_requirements_file.exists():
+ missing_packages = _check_requirements(android_requirements_file)
+ if missing_packages:
+ print("The following packages are required but not installed:")
+ for package in missing_packages:
+ print(f" - {package}")
+ print("Please install them using:")
+ print(f" pip install -r {android_requirements_file}")
+ sys.exit(1)
pyside_script_wrapper("android_deploy.py")
jinja2
pkginfo
tqdm
+packaging==24.1
set(pyside_MAJOR_VERSION "6")
-set(pyside_MINOR_VERSION "8")
-set(pyside_MICRO_VERSION "2.1")
+set(pyside_MINOR_VERSION "9")
+set(pyside_MICRO_VERSION "2")
set(pyside_PRE_RELEASE_VERSION_TYPE "")
set(pyside_PRE_RELEASE_VERSION "")
add_subdirectory(libpysideqml)
endif()
+if(Qt${QT_MAJOR_VERSION}RemoteObjects_FOUND)
+ add_subdirectory(libpysideremoteobjects)
+endif()
+
if(Qt${QT_MAJOR_VERSION}UiTools_FOUND)
add_subdirectory(plugins/uitools)
find_package(Qt6 COMPONENTS Designer)
DESTINATION share/PySide6${pyside_SUFFIX}/typesystems)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/templates/datavisualization_common.xml
DESTINATION share/PySide6${pyside_SUFFIX}/typesystems)
-install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/templates/opengl_common.xml
- DESTINATION share/PySide6${pyside_SUFFIX}/typesystems)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pyside6_global.h
DESTINATION include/${BINDING_NAME}${pyside6_SUFFIX})
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-->
-<typesystem package="PySide6.Qt3DAnimation" doc-package="PySide6.Qt3D"
+<typesystem package="PySide6.Qt3DAnimation" doc-package="PySide6.Qt3D" doc-mode="flat"
namespace-begin="QT_BEGIN_NAMESPACE" namespace-end="QT_END_NAMESPACE">
<load-typesystem name="Qt3DRender/typesystem_3drender.xml" generate="no"/>
<namespace-type name="Qt3DAnimation">
def default_exception_handler(self, context: dict[str, Any]) -> None:
# TODO
if context["message"]:
- print(context["message"])
+ print(f"{context['message']} from task {context['task']._name},"
+ "read the following traceback:")
+ print(context["traceback"])
def call_exception_handler(self, context: dict[str, Any]) -> None:
if self._exception_handler is not None:
from . import events
from . import futures
+import traceback
from typing import Any
self._cancelled = False # PYSIDE-2644; see _step
self._cancel_count = 0
self._cancel_message: str | None = None
+ # Store traceback in case of Exception. Useful when exception happens in coroutine
+ self._tb: str = None
# https://docs.python.org/3/library/asyncio-extending.html#task-lifetime-support
asyncio._register_task(self) # type: ignore[arg-type]
except BaseException as e:
self._state = futures.QAsyncioFuture.FutureState.DONE_WITH_EXCEPTION
self._exception = e
+ self._tb = traceback.format_exc()
else:
if asyncio.futures.isfuture(result):
# If the coroutine yields a future, the task will await its
"task": self,
"future": (exception_or_future
if asyncio.futures.isfuture(exception_or_future)
- else None)
+ else None),
+ "traceback": self._tb
})
if self.done():
${QtCore_GEN_DIR}/qxmlstreamnotationdeclaration_wrapper.cpp
${QtCore_GEN_DIR}/qxmlstreamreader_wrapper.cpp
${QtCore_GEN_DIR}/qxmlstreamwriter_wrapper.cpp
+${QtCore_GEN_DIR}/qmessagelogger_wrapper.cpp
${SPECIFIC_OS_FILES}
# module is always needed
// that has added any python fields or slots to its object layout.
// See https://mail.python.org/pipermail/python-list/2009-January/520733.html
if (type->tp_bases) {
- for (Py_ssize_t i = 0, size = PyTuple_Size(type->tp_bases); i < size; ++i) {
+ const auto size = PyTuple_Size(type->tp_bases);
+ Py_ssize_t i = 0;
+ // PYSIDE-1887, PYSIDE-86: Skip QObject base class of QGraphicsObject;
+ // it needs to use always QGraphicsItem as a QVariant type for
+ // QGraphicsItem::itemChange() to work.
+ if (qstrcmp(typeName, "QGraphicsObject*") == 0 && size > 1) {
+ auto *firstBaseType = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(type->tp_bases, 0));
+ if (SbkObjectType_Check(firstBaseType)) {
+ const char *firstBaseTypeName = Shiboken::ObjectType::getOriginalName(firstBaseType);
+ if (firstBaseTypeName != nullptr && qstrcmp(firstBaseTypeName, "QObject*") == 0)
+ ++i;
+ }
+ }
+ for ( ; i < size; ++i) {
auto baseType = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(type->tp_bases, i));
const QMetaType derived = QVariant_resolveMetaType(baseType);
if (derived.isValid())
<include file-name="qtcorehelper.h" location="local"/>
</extra-includes>
- <function signature="qFastCos(qreal)" since="4.6"/>
- <function signature="qFastSin(qreal)" since="4.6"/>
+ <overload-removal type="QString" replaces="QStringView"/>
+ <overload-removal type="double" replaces="float"/>
+ <overload-removal type="unsigned int" replaces="unsigned short"/> <!-- Order is important here -->
+ <overload-removal type="unsigned long" replaces="unsigned short"/>
+ <overload-removal type="unsigned" replaces="unsigned short"/>
+ <overload-removal type="unsigned long long" replaces="unsigned;unsigned int;unsigned long"/>
+ <overload-removal type="int" replaces="short;std::chrono::milliseconds;std::chrono::seconds"/>
+ <overload-removal type="long" replaces="short"/>
+ <overload-removal type="long long" replaces="long;int"/>
+
+ <function signature="qFastCos(qreal)"/>
+ <function signature="qFastSin(qreal)"/>
<function signature="qFuzzyCompare(double,double)"/>
- <function signature="qFuzzyIsNull(double)" since="4.6"/>
+ <function signature="qFuzzyIsNull(double)"/>
<function signature="qIsFinite(double)"/>
<function signature="qIsInf(double)"/>
<function signature="qIsNaN(double)"/>
<function signature="qIsNull(double)"/>
<!-- Qt5: gone <function signature="qRound(qreal)"/> -->
- <function signature="qtTrId(const char*,int)" since="4.6"/>
+ <function signature="qtTrId(const char*,int)"/>
<function signature="qVersion()">
<modify-function>
<modify-argument index="return" pyi-type="str"/>
<rejection class="*" argument-type="Qt::Initialization"/>
<rejection class="*" argument-type="FILE*"/>
- <rejection class="*" argument-type="std::chrono::milliseconds"/>
<rejection class="*" argument-type="^std::nullptr_t&?$"/>
<rejection class="*" argument-type="^std::initializer_list<.*$"/>
<primitive-type name="Qt::HANDLE" target-lang-api-name="PyLong"/>
<?endif?>
+ <primitive-type name="std::chrono::milliseconds" target-lang-api-name="PyLong">
+ <extra-includes>
+ <include file-name="chrono" location="global"/>
+ </extra-includes>
+ <conversion-rule>
+ <native-to-target file="../glue/qtcore.cpp" snippet="chrono-to-pylong"/>
+ <target-to-native>
+ <add-conversion type="PyLong" file="../glue/qtcore.cpp"
+ snippet="pylong-to-chrono"/>
+ </target-to-native>
+ </conversion-rule>
+ </primitive-type>
+
+ <primitive-type name="std::chrono::seconds" target-lang-api-name="PyLong">
+ <extra-includes>
+ <include file-name="chrono" location="global"/>
+ </extra-includes>
+ <conversion-rule>
+ <native-to-target file="../glue/qtcore.cpp" snippet="chrono-to-pylong"/>
+ <target-to-native>
+ <add-conversion type="PyLong" file="../glue/qtcore.cpp"
+ snippet="pylong-to-chrono"/>
+ </target-to-native>
+ </conversion-rule>
+ </primitive-type>
+
<!-- Qt5: add the new pointer-ish types -->
<primitive-type name="qintptr" target-lang-api-name="PyLong">
<conversion-rule>
<include file-name="QtCore/QProperty" location="global"/>
</extra-includes>
<enum-type name="AlignmentFlag" python-type="IntFlag" flags="Alignment"/>
- <enum-type name="AnchorPoint" since="4.6"/>
+ <enum-type name="AnchorPoint"/>
<enum-type name="ColorScheme" since="6.5"/>
<enum-type name="ApplicationAttribute"/>
<enum-type name="ApplicationState" flags="ApplicationStates"/>
<enum-type name="ConnectionType"/>
<enum-type name="ContextMenuPolicy"/>
<enum-type name="ContextMenuTrigger" since="6.8"/>
- <enum-type name="CoordinateSystem" since="4.6"/>
+ <enum-type name="CoordinateSystem"/>
<enum-type name="Corner"/>
<enum-type name="CursorShape"/>
<enum-type name="DateFormat"/>
<enum-type name="FindChildOption" flags="FindChildOptions"/>
<enum-type name="FocusPolicy" python-type="IntFlag"/>
<enum-type name="FocusReason"/>
- <enum-type name="GestureFlag" flags="GestureFlags" since="4.6"/>
- <enum-type name="GestureState" since="4.6"/>
- <enum-type name="GestureType" python-type="IntEnum" since="4.6"/>
+ <enum-type name="GestureFlag" flags="GestureFlags"/>
+ <enum-type name="GestureState"/>
+ <enum-type name="GestureType" python-type="IntEnum"/>
<enum-type name="GlobalColor"/>
<enum-type name="HighDpiScaleFactorRoundingPolicy"/>
<enum-type name="HitTestAccuracy"/>
<enum-type name="ImageConversionFlag" flags="ImageConversionFlags"/>
- <enum-type name="InputMethodHint" flags="InputMethodHints" since="4.6"/>
+ <enum-type name="InputMethodHint" flags="InputMethodHints"/>
<enum-type name="InputMethodQuery" flags="InputMethodQueries"/>
<enum-type name="EnterKeyType"/>
<enum-type name="ItemDataRole" python-type="IntEnum"/>
<enum-type name="MouseEventFlag" flags="MouseEventFlags"/>
<enum-type name="MouseEventSource"/>
<enum-type name="NativeGestureType"/>
- <enum-type name="NavigationMode" since="4.6"/>
+ <enum-type name="NavigationMode"/>
<enum-type name="Orientation" flags="Orientations"/>
<enum-type name="PenCapStyle"/>
<enum-type name="PenJoinStyle"/>
<enum-type name="TextFlag" python-type="IntFlag"/>
<enum-type name="TextFormat"/>
<enum-type name="TextInteractionFlag" flags="TextInteractionFlags"/>
- <enum-type name="TileRule" since="4.6"/>
+ <enum-type name="TileRule"/>
<enum-type name="TimerId" since="6.8"/>
<enum-type name="TimerType"/>
<enum-type name="TimeSpec"/>
<enum-type name="ToolBarArea" flags="ToolBarAreas"/>
<enum-type name="ToolBarAreaSizes"/>
<enum-type name="ToolButtonStyle"/>
- <enum-type name="TouchPointState" flags="TouchPointStates" since="4.6"/>
+ <enum-type name="TouchPointState" flags="TouchPointStates"/>
<enum-type name="TransformationMode"/>
<enum-type name="UIEffect"/>
<enum-type name="WhiteSpaceMode"/>
<enum-type name="WindowModality"/>
<enum-type name="WindowState" flags="WindowStates"/>
<enum-type name="WindowType" python-type="IntFlag" flags="WindowFlags"/>
- <enum-type name="CursorMoveStyle" since="4.8" revision="4800"/>
+ <enum-type name="CursorMoveStyle" revision="4800"/>
<inject-code class="target" position="end" file="../glue/qtcore.cpp"
snippet="qt-modifier"/>
<enum-type name="ForeverConstant"/>
</value-type>
- <value-type name="QElapsedTimer" since="4.7">
- <enum-type name="ClockType" since="4.7"/>
+ <value-type name="QElapsedTimer">
+ <enum-type name="ClockType"/>
</value-type>
<object-type name="QAbstractTableModel"
<insert-template name="return_internal_pointer" />
</inject-code>
</modify-function>
+ <!-- Remove const variation in favor of using internalPointer -->
+ <modify-function signature="constInternalPointer()const" remove="all"/>
<modify-function signature="operator QModelIndex()const">
<modify-argument index="return">
<parent index="this" action="add"/>
<enum-type name="LanguageCodeType" python-type="IntFlag" flags="LanguageCodeTypes" since="6.3"/>
<enum-type name="MeasurementSystem"/>
<enum-type name="NumberOption" flags="NumberOptions"/>
- <enum-type name="Script" since="4.8" revision="4800"/>
- <enum-type name="CurrencySymbolFormat" since="4.8" revision="4800"/>
- <enum-type name="QuotationStyle" since="4.8" revision="4800"/>
+ <enum-type name="Script" revision="4800"/>
+ <enum-type name="CurrencySymbolFormat" revision="4800"/>
+ <enum-type name="QuotationStyle" revision="4800"/>
<!--### All those C++ number types have the same representation in Python -->
<modify-function signature="toString(qulonglong)const" remove="all"/>
<modify-function signature="toString(ushort)const" remove="all"/>
<configuration condition="QT_CONFIG(thread)"/>
<inject-code file="../glue/qtcore.cpp" class="native" position="beginning" snippet="qthread_pthread_cleanup"/>
<enum-type name="Priority"/>
+ <enum-type name="QualityOfService" since="6.9"/>
<modify-function signature="currentThreadId()" remove="all"/>
<modify-function signature="run()" allow-thread="yes">
<inject-code file="../glue/qtcore.cpp" class="native" position="beginning"
<inject-documentation format="target" mode="append"
file="../doc/qtcore.rst" snippet="qobject-findChild"/>
<inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qobject-findchild-2"/>
- <modify-argument index="return" pyi-type="Optional[PlaceHolderType]">
- <parent index="this" action="add"/>
- </modify-argument>
+ <modify-argument index="return" pyi-type="Optional[PlaceholderType]">
+ <parent index="this" action="add"/>
+ </modify-argument>
+ <modify-argument index="1" pyi-type="typing.Type[PlaceholderType]">
+ </modify-argument>
</add-function>
<add-function signature="findChildren(PyTypeObject*@type@,const QString&@name@={},Qt::FindChildOptions@options@=Qt::FindChildrenRecursively)"
return-type="PySequence*" >
Like the method *findChild*, the first parameter should be the child's type.
</inject-documentation>
<inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qobject-findchildren"/>
- <modify-argument index="return" pyi-type="Iterable[PlaceHolderType]">
+ <modify-argument index="return" pyi-type="List[PlaceholderType]">
<parent index="this" action="add"/>
</modify-argument>
+ <modify-argument index="1" pyi-type="typing.Type[PlaceholderType]">
+ </modify-argument>
</add-function>
<add-function signature="findChildren(PyTypeObject*@type@,const QRegularExpression&@pattern@,Qt::FindChildOptions@options@=Qt::FindChildrenRecursively)"
return-type="PySequence*" >
<inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qobject-findchildren"/>
- <modify-argument index="return" pyi-type="Iterable[PlaceHolderType]">
+ <modify-argument index="return" pyi-type="List[PlaceholderType]">
<parent index="this" action="add"/>
</modify-argument>
+ <modify-argument index="1" pyi-type="typing.Type[PlaceholderType]">
+ </modify-argument>
</add-function>
<add-function signature="tr(const char *@sourceText@, const char *@disambiguation@=nullptr, int @n@=-1)" return-type="QString" classmethod="yes">
<insert-template name="return_native_eventfilter_conversion"/>
</conversion-rule>
</modify-argument>
- <inject-code position="end">
- <insert-template name="return_native_eventfilter"/>
- </inject-code>
+ <inject-code position="end"
+ file="../glue/qtcore.cpp" snippet="return-native-eventfilter"/>
</modify-function>
</object-type>
<inject-code file="../glue/qtcore.cpp" snippet="qcryptographichash-adddata"/>
</modify-function>
</object-type>
- <value-type name="QOperatingSystemVersionBase" since="6.3">
- <enum-type name="OSType"/>
- </value-type>
+ <value-type name="QOperatingSystemVersionBase" since="6.3"
+ default-constructor="QOperatingSystemVersionBase(QOperatingSystemVersionBase::Unknown, 1)"/>
<value-type name="QOperatingSystemVersionUnexported" since="6.3" generate="false"/>
<value-type name="QOperatingSystemVersion">
+ <enum-type name="OSType"/>
<modify-function signature="QOperatingSystemVersion(const QOperatingSystemVersionBase&)" remove="all"/>
</value-type>
<object-type name="QLibrary">
</object-type>
<object-type name="QLibraryInfo">
<enum-type name="LibraryPath"/>
+ <inject-code class="native" position="beginning"
+ file="../glue/qtcore.cpp" snippet="qlibraryinfo_python_build"/>
<modify-function signature="build()">
<inject-code class="target" position="end" file="../glue/qtcore.cpp" snippet="qlibraryinfo_build"/>
</modify-function>
<configuration condition="QT_CONFIG(thread)"/>
<modify-function signature="lock()" allow-thread="yes"/>
<modify-function signature="tryLock()" allow-thread="yes"/>
+ <modify-field name="FutexAlwaysAvailable" remove="yes"/>
</object-type>
<object-type name="QMutex">
<object-type name="QCommandLineParser">
<enum-type name="OptionsAfterPositionalArgumentsMode"/>
<enum-type name="SingleDashWordOptionMode"/>
+ <enum-type name="MessageType" since="6.9"/>
</object-type>
<object-type name="QCoreApplication">
<!--Qt5: gone <enum-type name="Encoding"/> -->
- <enum-type identified-by-value="ApplicationFlags" since="4.8" revision="4800"/>
+ <enum-type identified-by-value="ApplicationFlags" revision="4800"/>
<extra-includes>
<include file-name="QStringList" location="global"/>
<include file-name="QTranslator" location="global"/>
<object-type name="QDataStream" stream="yes">
- <enum-type name="FloatingPointPrecision" since="4.6"/>
+ <enum-type name="FloatingPointPrecision"/>
<enum-type name="Status"/>
<enum-type name="Version" python-type="IntEnum"/>
<enum-type name="ByteOrder"/>
<enum-type name="LocateOption" flags="LocateOptions"/>
</object-type>
<object-type name="QSystemSemaphore">
- <configuration condition="#ifndef QT_NO_SYSTEMSEMAPHORE"/>
+ <configuration condition="#if QT_CONFIG(systemsemaphore)" />
<enum-type name="AccessMode"/>
<enum-type name="SystemSemaphoreError"/>
</object-type>
<object-type name="QXmlStreamReader">
<enum-type name="Error"/>
<enum-type name="TokenType"/>
- <enum-type name="ReadElementTextBehaviour" since="4.6"/>
+ <enum-type name="ReadElementTextBehaviour"/>
</object-type>
<object-type name="QXmlStreamWriter">
<!-- Removed because it expect QString to be mutable -->
<add-function signature="__repr__" return-type="str">
<inject-code class="target" position="beginning" file="../glue/qtcore.cpp" snippet="qmetaobject-repr"/>
</add-function>
- <modify-function signature="indexOfClassInfo(const char *)">
+ <modify-function signature="indexOfClassInfo(const char *)const">
<modify-argument index="1" pyi-type="str"/>
</modify-function>
- <modify-function signature="indexOfConstructor(const char *)">
+ <modify-function signature="indexOfConstructor(const char *)const">
<modify-argument index="1" pyi-type="str"/>
</modify-function>
- <modify-function signature="indexOfEnumerator(const char *)">
+ <modify-function signature="indexOfEnumerator(const char *)const">
<modify-argument index="1" pyi-type="str"/>
</modify-function>
- <modify-function signature="indexOfMethod(const char *)">
+ <modify-function signature="indexOfMethod(const char *)const">
<modify-argument index="1" pyi-type="str"/>
</modify-function>
- <modify-function signature="indexOfProperty(const char *)">
+ <modify-function signature="indexOfProperty(const char *)const">
<modify-argument index="1" pyi-type="str"/>
</modify-function>
- <modify-function signature="indexOfSignal(const char *)">
+ <modify-function signature="indexOfSignal(const char *)const">
<modify-argument index="1" pyi-type="str"/>
</modify-function>
- <modify-function signature="indexOfSlot(const char *)">
+ <modify-function signature="indexOfSlot(const char *)const">
<modify-argument index="1" pyi-type="str"/>
</modify-function>
</object-type>
</value-type>
<!-- From Qt4.6 -->
- <object-type name="QAbstractAnimation" since="4.6">
+ <object-type name="QAbstractAnimation">
<enum-type name="DeletionPolicy"/>
<enum-type name="Direction"/>
<enum-type name="State"/>
</object-type>
- <object-type name="QAnimationGroup" since="4.6">
+ <object-type name="QAnimationGroup">
<modify-function signature="addAnimation(QAbstractAnimation*)">
<modify-argument index="1">
<parent index="this" action="add"/>
<!-- We will use inject code to implement the function below -->
<rejection class="QEasingCurve" function-name="setCustomType"/>
<rejection class="QEasingCurve" function-name="customType"/>
- <value-type name="QEasingCurve" since="4.6">
+ <value-type name="QEasingCurve">
<extra-includes>
<include file-name="pysideweakref.h" location="global"/>
<include file-name="glue/qeasingcurve_glue.h" location="local"/>
</extra-includes>
</value-type>
- <value-type name="QMargins" since="4.6"/>
+ <value-type name="QMargins"/>
<value-type name="QMarginsF"/>
- <object-type name="QParallelAnimationGroup" since="4.6"/>
+ <object-type name="QParallelAnimationGroup"/>
- <object-type name="QPauseAnimation" since="4.6"/>
+ <object-type name="QPauseAnimation"/>
- <value-type name="QProcessEnvironment" since="4.6">
+ <value-type name="QProcessEnvironment">
<configuration condition="QT_CONFIG(processenvironment)"/>
<enum-type name="Initialization" since="6.3"/>
</value-type>
- <object-type name="QPropertyAnimation" since="4.6"/>
+ <object-type name="QPropertyAnimation"/>
- <object-type name="QSequentialAnimationGroup" since="4.6"/>
+ <object-type name="QSequentialAnimationGroup"/>
- <object-type name="QVariantAnimation" since="4.6"/>
+ <object-type name="QVariantAnimation"/>
<value-type name="QVersionNumber">
<modify-function signature="fromString(QAnyStringView,qsizetype*)">
<object-type name="QStringListModel"/>
<object-type name="QSharedMemory">
- <configuration condition="#ifndef QT_NO_SHAREDMEMORY"/>
+ <configuration condition="#if QT_CONFIG(sharedmemory)" />
<enum-type name="AccessMode"/>
<enum-type name="SharedMemoryError"/>
<modify-function signature="data()">
</inject-code>
</add-function>
+ <object-type name="QMessageLogger">
+ <inject-documentation format="target" mode="append"
+ file="../doc/qtcore.rst" snippet="qmessagelogger"/>
+ <modify-function signature="debug(const char*)const">
+ <inject-code file="../glue/qtcore.cpp" snippet="qmessagelogger-format-string"/>
+ <modify-argument index="1" pyi-type="str"/>
+ </modify-function>
+ <modify-function signature="debug(const QLoggingCategory&,const char*)const">
+ <inject-code file="../glue/qtcore.cpp" snippet="qmessagelogger-logcategory-format-string"/>
+ <modify-argument index="2" pyi-type="str"/>
+ </modify-function>
+ <modify-function signature="info(const char*)const">
+ <inject-code file="../glue/qtcore.cpp" snippet="qmessagelogger-format-string"/>
+ <modify-argument index="1" pyi-type="str"/>
+ </modify-function>
+ <modify-function signature="info(const QLoggingCategory&,const char*)const">
+ <inject-code file="../glue/qtcore.cpp" snippet="qmessagelogger-logcategory-format-string"/>
+ <modify-argument index="2" pyi-type="str"/>
+ </modify-function>
+ <modify-function signature="warning(const char*)const">
+ <inject-code file="../glue/qtcore.cpp" snippet="qmessagelogger-format-string"/>
+ <modify-argument index="1" pyi-type="str"/>
+ </modify-function>
+ <modify-function signature="warning(const QLoggingCategory&,const char*)const">
+ <inject-code file="../glue/qtcore.cpp" snippet="qmessagelogger-logcategory-format-string"/>
+ <modify-argument index="2" pyi-type="str"/>
+ </modify-function>
+ <modify-function signature="fatal(const char*)const">
+ <inject-code file="../glue/qtcore.cpp" snippet="qmessagelogger-format-string"/>
+ <modify-argument index="1" pyi-type="str"/>
+ </modify-function>
+ <modify-function signature="fatal(const QLoggingCategory&,const char*)const">
+ <inject-code file="../glue/qtcore.cpp" snippet="qmessagelogger-logcategory-format-string"/>
+ <modify-argument index="2" pyi-type="str"/>
+ </modify-function>
+ <modify-function signature="critical(const char*)const">
+ <inject-code file="../glue/qtcore.cpp" snippet="qmessagelogger-format-string"/>
+ <modify-argument index="1" pyi-type="str"/>
+ </modify-function>
+ <modify-function signature="critical(const QLoggingCategory&,const char*)const">
+ <inject-code file="../glue/qtcore.cpp" snippet="qmessagelogger-logcategory-format-string"/>
+ <modify-argument index="2" pyi-type="str"/>
+ </modify-function>
+ <modify-function signature="noDebug(const char*)const">
+ <inject-code file="../glue/qtcore.cpp" snippet="qmessagelogger-format-string"/>
+ <modify-argument index="1" pyi-type="str"/>
+ </modify-function>
+ </object-type>
+
<suppress-warning text="^.*enum 'Qt::Initialization' does not have a type entry.*$"/>
<suppress-warning text="^.*Enum 'QRandomGenerator::System'.*does not have a type entry.*$"/>
${QtGraphs_GEN_DIR}/qscatter3dseries_wrapper.cpp
${QtGraphs_GEN_DIR}/qscatterdataitem_wrapper.cpp
${QtGraphs_GEN_DIR}/qscatterdataproxy_wrapper.cpp
+${QtGraphs_GEN_DIR}/qspline3dseries_wrapper.cpp
${QtGraphs_GEN_DIR}/qsurface3dseries_wrapper.cpp
${QtGraphs_GEN_DIR}/qsurfacedataitem_wrapper.cpp
${QtGraphs_GEN_DIR}/qsurfacedataproxy_wrapper.cpp
<load-typesystem name="QtQuick/typesystem_quick.xml" generate="no" />
- <load-typesystem name="templates/datavisualization_common.xml" generate="no" />
-
<function signature="qDefaultSurfaceFormat(bool)"/>
<namespace-type name="QtGraphs3D">
<enum-type name="RenderingMode"/>
<enum-type name="SelectionFlag" flags="SelectionFlags"/>
<enum-type name="ShadowQuality"/>
+ <enum-type name="TransparencyTechnique" since="6.7"/>
<enum-type name="CameraPreset" since="6.7"/>
</namespace-type>
<object-type name="QScatterDataProxy">
</object-type>
<object-type name="QSplineSeries" since="6.8"/>
+ <object-type name="QSpline3DSeries" since="6.9"/>
<object-type name="QSurface3DSeries">
<enum-type name="Shading" since="6.8"/>
<enum-type name="DrawFlag" flags="DrawFlags"/>
${QtGui_GEN_DIR}/qfontinfo_wrapper.cpp
${QtGui_GEN_DIR}/qfontmetrics_wrapper.cpp
${QtGui_GEN_DIR}/qfontmetricsf_wrapper.cpp
+${QtGui_GEN_DIR}/qfontvariableaxis_wrapper.cpp
${QtGui_GEN_DIR}/qglyphrun_wrapper.cpp
${QtGui_GEN_DIR}/qgradient_wrapper.cpp
${QtGui_GEN_DIR}/qguiapplication_wrapper.cpp
${QtGui_GEN_DIR}/qpainterpath_element_wrapper.cpp
${QtGui_GEN_DIR}/qpainterpath_wrapper.cpp
${QtGui_GEN_DIR}/qpainterpathstroker_wrapper.cpp
+${QtGui_GEN_DIR}/qpainterstateguard_wrapper.cpp
${QtGui_GEN_DIR}/qpaintevent_wrapper.cpp
${QtGui_GEN_DIR}/qpalette_wrapper.cpp
${QtGui_GEN_DIR}/qpdfoutputintent_wrapper.cpp
<load-typesystem name="templates/common.xml" generate="no"/>
<load-typesystem name="templates/core_common.xml" generate="no"/>
<load-typesystem name="templates/gui_common.xml" generate="no"/>
- <load-typesystem name="templates/opengl_common.xml" generate="no"/>
<rejection class="^Q.*$" argument-type="^QPlatform.*$"/>
<function signature="qAlpha(uint)"/>
<object-type name="QAction">
<enum-type name="ActionEvent"/>
<enum-type name="MenuRole"/>
- <enum-type name="Priority" since="4.6"/>
+ <enum-type name="Priority"/>
<!-- PYSIDE-1627 QAction::menu()/setMenu() are templates -->
<add-function signature="menu()const" return-type="QObject*">
<inject-code file="../glue/qtgui.cpp" snippet="qaction-menu"/>
</modify-function>
</value-type>
<value-type name="QTextBlockFormat">
- <enum-type name="LineHeightTypes" since="4.8" revision="4800"/>
+ <enum-type name="LineHeightTypes" revision="4800"/>
<enum-type name="MarkerType"/>
</value-type>
<value-type name="QTextTableCellFormat"/>
<modify-argument index="1">
<replace-type modified-type="PySequence"/>
</modify-argument>
- <inject-code class="target" position="beginning">
- <insert-template name="load_xpm"/>
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="../glue/qtgui.cpp" snippet="qpixmap-load-xpm"/>
</modify-function>
<modify-function signature="QPixmap(const QString &, const char *, QFlags<Qt::ImageConversionFlag>)">
<modify-argument index="1"><replace-type modified-type="PyPathLike"/></modify-argument>
<enum-type name="StyleHint"/>
<enum-type name="StyleStrategy" python-type="Flag"/>
<enum-type name="Weight" python-type="IntEnum"/>
- <enum-type name="HintingPreference" since="4.8" revision="4800"/>
+ <enum-type name="HintingPreference" revision="4800"/>
<extra-includes>
<include file-name="QStringList" location="global"/>
</extra-includes>
- <value-type name="Tag" since="6.7"/>
+ <value-type name="Tag" since="6.7">
+ <inject-code class="native" position="beginning"
+ file="../glue/qtgui.cpp" snippet="qfont-tag-from-str-helper"/>
+ <add-function signature="Tag(QString@name@)">
+ <inject-code class="target" position="beginning"
+ file="../glue/qtgui.cpp" snippet="qfont-tag-init-str"/>
+ </add-function>
+ <add-function signature="fromString(QString@name@)"
+ static="true" return-type="QFont::Tag">
+ <inject-code class="target" position="beginning"
+ file="../glue/qtgui.cpp" snippet="qfont-tag-fromString"/>
+ </add-function>
+ <add-function signature="fromValue(int@value@)"
+ static="true" return-type="QFont::Tag">
+ <inject-code class="target" position="beginning"
+ file="../glue/qtgui.cpp" snippet="qfont-tag-fromValue"/>
+ </add-function>
+ </value-type>
<!-- PYSIDE-1685: QFont(QString) should be checked first, else it will be interpreted as sequence -->
<modify-function signature="QFont(const QString&,int,int, bool)" overload-number="0"/>
<modify-function signature="QFont(const QStringList &,int,int, bool)" overload-number="1"/>
</modify-argument>
</modify-function>
</value-type>
+ <value-type name="QFontVariableAxis" since="6.9"/>
<value-type name="QTextTableCell" >
<extra-includes>
<include file-name="QTextCursor" location="global"/>
<modify-argument index="1">
<replace-type modified-type="PySequence"/>
</modify-argument>
- <inject-code class="target" position="beginning">
- <insert-template name="load_xpm"/>
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="../glue/qtgui.cpp" snippet="qpixmap-load-xpm"/>
</modify-function>
<!-- ### There is already an fromData with a QByteArray type (that is convertible from Python's str) as the first type. -->
<modify-function signature="fromData(const uchar*,int,const char*)" remove="all"/>
<!-- ### There is already an loadFromData with a QByteArray type (that is convertible from Python's str) as the first type. -->
<modify-function signature="loadFromData(const uchar*,int,const char*)" remove="all"/>
- <modify-function signature="constBits()const" since="4.7">
+ <modify-function signature="constBits()const">
<inject-code file="../glue/qtgui.cpp" snippet="qimage-constbits"/>
</modify-function>
<modify-function signature="bits()">
<inject-code file="../glue/qtgui.cpp" snippet="qimage-bits"/>
</modify-function>
- <modify-function signature="constScanLine(int)const" since="4.7">
+ <modify-function signature="constScanLine(int)const">
<inject-code file="../glue/qtgui.cpp" snippet="qimage-constscanline"/>
<modify-argument index="return">
<replace-type modified-type="PyObject"/>
<include file-name="QVariant" location="global"/>
</extra-includes>
<add-function signature="__repr__" return-type="str">
- <inject-code class="target" position="beginning">
- <insert-template name="qcolor_repr"/>
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="../glue/qtgui.cpp" snippet="qcolor-repr"/>
</add-function>
<add-function signature="__str__" return-type="str">
- <inject-code class="target" position="beginning">
- <insert-template name="qcolor_repr"/>
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="../glue/qtgui.cpp" snippet="qcolor-repr"/>
</add-function>
<add-function signature="__setstate__(PyObject*)" return-type="PyObject">
<inject-code file="../glue/qtgui.cpp" snippet="qcolor-setstate"/>
</insert-template>
</inject-code>
</modify-function>
- <modify-function signature="getHsl(int*,int*,int*,int*)const" since="4.6">
+ <modify-function signature="getHsl(int*,int*,int*,int*)const">
<modify-argument index="0">
<replace-type modified-type="PyObject*"/>
</modify-argument>
</insert-template>
</inject-code>
</modify-function>
- <modify-function signature="getHslF(float*,float*,float*,float*)const" since="4.6">
+ <modify-function signature="getHslF(float*,float*,float*,float*)const">
<modify-argument index="0">
<replace-type modified-type="PyObject*"/>
</modify-argument>
<modify-argument index="1">
<replace-type modified-type="char"/>
</modify-argument>
- <inject-code class="target" position="beginning">
- <insert-template name="QFontCharFix"/>
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="../glue/qtgui.cpp" snippet="qfontmetrics-qfontcharfix"/>
</modify-function>
<modify-function signature="horizontalAdvance(QChar)const" rename="horizontalAdvanceChar">
<modify-argument index="1">
<replace-type modified-type="char"/>
</modify-argument>
- <inject-code class="target" position="beginning">
- <insert-template name="QFontCharFix"/>
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="../glue/qtgui.cpp" snippet="qfontmetrics-qfontcharfix"/>
</modify-function>
<modify-function signature="boundingRect(QRectF,int,QString,int,int*)const">
<modify-argument index="1">
<replace-type modified-type="char"/>
</modify-argument>
- <inject-code class="target" position="beginning">
- <insert-template name="QFontCharFix"/>
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="../glue/qtgui.cpp" snippet="qfontmetrics-qfontcharfix"/>
</modify-function>
<modify-function signature="horizontalAdvance(QChar)const" rename="horizontalAdvanceChar">
<modify-argument index="1">
<replace-type modified-type="char"/>
</modify-argument>
- <inject-code class="target" position="beginning">
- <insert-template name="QFontCharFix"/>
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="../glue/qtgui.cpp" snippet="qfontmetrics-qfontcharfix"/>
</modify-function>
<modify-function signature="boundingRect(int,int,int,int,int,QString,int,int*)const">
</object-type>
<object-type name="QPyTextObject"/>
- <object-type name="QDesktopServices" since="4.2">
+ <object-type name="QDesktopServices">
<modify-function signature="openUrl(const QUrl&)" allow-thread="yes"/>
</object-type>
<object-type name="QDoubleValidator">
<enum-type name="MarkdownFeature" flags="MarkdownFeatures"/>
<enum-type name="MetaInformation"/>
<enum-type name="ResourceType" python-type="IntEnum"/>
- <enum-type name="Stacks" since="4.7"/>
+ <enum-type name="Stacks"/>
<modify-function signature="setDocumentLayout(QAbstractTextDocumentLayout*)">
<modify-argument index="1">
<parent index="this" action="add"/>
</modify-function>
<modify-function signature="print(QPagedPaintDevice*)const" allow-thread="yes" rename="print_"/>
</object-type>
- <object-type name="QTextDocumentWriter" since="4.5"/>
+ <object-type name="QTextDocumentWriter"/>
<object-type name="QTextTable">
<extra-includes>
<include file-name="QTextCursor" location="global"/>
<enum-type name="PaintEngineFeature" flags="PaintEngineFeatures"/>
<enum-type name="PolygonDrawMode"/>
<enum-type name="Type"/>
+
+ <modify-function signature="drawLines(const QLine*,int)">
+ <modify-argument index="1">
+ <replace-type modified-type="PySequence"/>
+ <conversion-rule class="native">
+ <insert-template name="pysequence-to-c-array"> <!-- Binding -->
+ <replace from="%TYPE" to="QLine"/>
+ </insert-template>
+ </conversion-rule>
+ <conversion-rule class="target"> <!-- Virtual override -->
+ <insert-template name="c-array-to-pysequence">
+ <replace from="%TYPE" to="QLine"/>
+ <replace from="%COUNT" to="lineCount"/>
+ </insert-template>
+ </conversion-rule>
+ </modify-argument>
+ <modify-argument index="2">
+ <remove-argument/>
+ <conversion-rule class="native"> <!-- Provide parameter for binding -->
+ <insert-template name="pysequencesize_int"/>
+ </conversion-rule>
+ </modify-argument>
+ </modify-function>
+
+ <modify-function signature="drawLines(const QLineF*,int)">
+ <modify-argument index="1">
+ <replace-type modified-type="PySequence"/>
+ <conversion-rule class="native">
+ <insert-template name="pysequence-to-c-array"> <!-- Binding -->
+ <replace from="%TYPE" to="QLineF"/>
+ </insert-template>
+ </conversion-rule>
+ <conversion-rule class="target"> <!-- Virtual override -->
+ <insert-template name="c-array-to-pysequence">
+ <replace from="%TYPE" to="QLineF"/>
+ <replace from="%COUNT" to="lineCount"/>
+ </insert-template>
+ </conversion-rule>
+ </modify-argument>
+ <modify-argument index="2">
+ <remove-argument/>
+ <conversion-rule class="native"> <!-- Provide parameter for binding -->
+ <insert-template name="pysequencesize_int"/>
+ </conversion-rule>
+ </modify-argument>
+ </modify-function>
+
+ <modify-function signature="drawPoints(const QPoint*,int)">
+ <modify-argument index="1">
+ <replace-type modified-type="PySequence"/>
+ <conversion-rule class="native">
+ <insert-template name="pysequence-to-c-array"> <!-- Binding -->
+ <replace from="%TYPE" to="QPoint"/>
+ </insert-template>
+ </conversion-rule>
+ <conversion-rule class="target"> <!-- Virtual override -->
+ <insert-template name="c-array-to-pysequence">
+ <replace from="%TYPE" to="QPoint"/>
+ <replace from="%COUNT" to="pointCount"/>
+ </insert-template>
+ </conversion-rule>
+ </modify-argument>
+ <modify-argument index="2">
+ <remove-argument/>
+ <conversion-rule class="native"> <!-- Provide parameter for binding -->
+ <insert-template name="pysequencesize_int"/>
+ </conversion-rule>
+ </modify-argument>
+ </modify-function>
+
+ <modify-function signature="drawPoints(const QPointF*,int)">
+ <modify-argument index="1">
+ <replace-type modified-type="PySequence"/>
+ <conversion-rule class="native">
+ <insert-template name="pysequence-to-c-array"> <!-- Binding -->
+ <replace from="%TYPE" to="QPointF"/>
+ </insert-template>
+ </conversion-rule>
+ <conversion-rule class="target"> <!-- Virtual override -->
+ <insert-template name="c-array-to-pysequence">
+ <replace from="%TYPE" to="QPointF"/>
+ <replace from="%COUNT" to="pointCount"/>
+ </insert-template>
+ </conversion-rule>
+ </modify-argument>
+ <modify-argument index="2">
+ <remove-argument/>
+ <conversion-rule class="native"> <!-- Provide parameter for binding -->
+ <insert-template name="pysequencesize_int"/>
+ </conversion-rule>
+ </modify-argument>
+ </modify-function>
+
+ <modify-function signature="drawPolygon(const QPoint*,int,QPaintEngine::PolygonDrawMode)">
+ <modify-argument index="1">
+ <replace-type modified-type="PySequence"/>
+ <conversion-rule class="native">
+ <insert-template name="pysequence-to-c-array"> <!-- Binding -->
+ <replace from="%TYPE" to="QPoint"/>
+ </insert-template>
+ </conversion-rule>
+ <conversion-rule class="target"> <!-- Virtual override -->
+ <insert-template name="c-array-to-pysequence">
+ <replace from="%TYPE" to="QPoint"/>
+ <replace from="%COUNT" to="pointCount"/>
+ </insert-template>
+ </conversion-rule>
+ </modify-argument>
+ <modify-argument index="2">
+ <remove-argument/>
+ <conversion-rule class="native"> <!-- Provide parameter for binding -->
+ <insert-template name="pysequencesize_int"/>
+ </conversion-rule>
+ </modify-argument>
+ </modify-function>
+
+ <modify-function signature="drawPolygon(const QPointF*,int,QPaintEngine::PolygonDrawMode)">
+ <modify-argument index="1">
+ <replace-type modified-type="PySequence"/>
+ <conversion-rule class="native">
+ <insert-template name="pysequence-to-c-array"> <!-- Binding -->
+ <replace from="%TYPE" to="QPointF"/>
+ </insert-template>
+ </conversion-rule>
+ <conversion-rule class="target"> <!-- Virtual override -->
+ <insert-template name="c-array-to-pysequence">
+ <replace from="%TYPE" to="QPointF"/>
+ <replace from="%COUNT" to="pointCount"/>
+ </insert-template>
+ </conversion-rule>
+ </modify-argument>
+ <modify-argument index="2">
+ <remove-argument/>
+ <conversion-rule class="native"> <!-- Provide parameter for binding -->
+ <insert-template name="pysequencesize_int"/>
+ </conversion-rule>
+ </modify-argument>
+ </modify-function>
+
+ <modify-function signature="drawRects(const QRect*,int)">
+ <modify-argument index="1">
+ <replace-type modified-type="PySequence"/>
+ <conversion-rule class="native">
+ <insert-template name="pysequence-to-c-array"> <!-- Binding -->
+ <replace from="%TYPE" to="QRect"/>
+ </insert-template>
+ </conversion-rule>
+ <conversion-rule class="target"> <!-- Virtual override -->
+ <insert-template name="c-array-to-pysequence">
+ <replace from="%TYPE" to="QRect"/>
+ <replace from="%COUNT" to="rectCount"/>
+ </insert-template>
+ </conversion-rule>
+ </modify-argument>
+ <modify-argument index="2">
+ <remove-argument/>
+ <conversion-rule class="native"> <!-- Provide parameter for binding -->
+ <insert-template name="pysequencesize_int"/>
+ </conversion-rule>
+ </modify-argument>
+ </modify-function>
+
+ <modify-function signature="drawRects(const QRectF*,int)">
+ <modify-argument index="1">
+ <replace-type modified-type="PySequence"/>
+ <conversion-rule class="native">
+ <insert-template name="pysequence-to-c-array"> <!-- Binding -->
+ <replace from="%TYPE" to="QRectF"/>
+ </insert-template>
+ </conversion-rule>
+ <conversion-rule class="target"> <!-- Virtual override -->
+ <insert-template name="c-array-to-pysequence">
+ <replace from="%TYPE" to="QRectF"/>
+ <replace from="%COUNT" to="rectCount"/>
+ </insert-template>
+ </conversion-rule>
+ </modify-argument>
+ <modify-argument index="2">
+ <remove-argument/>
+ <conversion-rule class="native"> <!-- Provide parameter for binding -->
+ <insert-template name="pysequencesize_int"/>
+ </conversion-rule>
+ </modify-argument>
+ </modify-function>
+
<modify-function signature="begin(QPaintDevice*)">
<modify-argument index="1" invalidate-after-use="yes"/>
</modify-function>
<include file-name="pyside_numpy.h" location="global"/>
</extra-includes>
<enum-type name="CompositionMode"/>
- <enum-type name="PixmapFragmentHint" flags="PixmapFragmentHints" since="4.7"/>
+ <enum-type name="PixmapFragmentHint" flags="PixmapFragmentHints"/>
<enum-type name="RenderHint" flags="RenderHints"/>
- <value-type name="PixmapFragment" since="4.7">
+ <value-type name="PixmapFragment">
<include file-name="QPainter" location="global"/>
</value-type>
<insert-template name="qpainter_drawlist"/>
</inject-code>
</add-function>
- <!-- ### Overloads using QList<T> does the job of these methods -->
- <modify-function signature="drawLines(const QLine*,int)" remove="all"/>
- <modify-function signature="drawLines(const QPoint*,int)" remove="all"/>
- <modify-function signature="drawLines(const QPointF*,int)" remove="all"/>
- <modify-function signature="drawRects(const QRect*,int)" remove="all"/>
- <!-- ### -->
+
<!-- PYSIDE-1540: Preferably use the QPolygon overloads first to avoid
a costly sequence type check on QPolygon. -->
<modify-function signature="drawPoints(const QPoint*,int)" remove="all"/>
</add-function>
</object-type>
+ <object-type name="QPainterStateGuard" since="6.9">
+ <enum-type name="InitialState"/>
+ <add-function signature="__enter__()" return-type="QPainterStateGuard">
+ <inject-code file="../glue/qtcore.cpp" snippet="default-enter"/>
+ </add-function>
+ <add-function signature="__exit__(PyObject*,PyObject*,PyObject*)">
+ <inject-code file="../glue/qtgui.cpp" snippet="qpainterstateguard-restore"/>
+ </add-function>
+ </object-type>
+
<value-type name="QGenericMatrix" generate="no"/>
- <value-type name="QMatrix2x2" since="4.6">
+ <value-type name="QMatrix2x2">
<modify-function signature="QMatrix2x2(const float*)" remove="all"/>
<modify-function signature="copyDataTo(float*) const" remove="all"/>
<modify-function signature="constData()const" remove="all"/>
</add-function>
</value-type>
- <value-type name="QMatrix2x3" since="4.6">
+ <value-type name="QMatrix2x3">
<modify-function signature="QMatrix2x3(const float*)" remove="all"/>
<modify-function signature="copyDataTo(float*) const" remove="all"/>
<modify-function signature="constData()const" remove="all"/>
</add-function>
</value-type>
- <value-type name="QMatrix2x4" since="4.6">
+ <value-type name="QMatrix2x4">
<modify-function signature="QMatrix2x4(const float*)" remove="all"/>
<modify-function signature="copyDataTo(float*) const" remove="all"/>
<modify-function signature="constData()const" remove="all"/>
</add-function>
</value-type>
- <value-type name="QMatrix3x2" since="4.6">
+ <value-type name="QMatrix3x2">
<modify-function signature="QMatrix3x2(const float*)" remove="all"/>
<modify-function signature="copyDataTo(float*) const" remove="all"/>
<modify-function signature="constData()const" remove="all"/>
</add-function>
</value-type>
- <value-type name="QMatrix3x3" since="4.6">
+ <value-type name="QMatrix3x3">
<modify-function signature="QMatrix3x3(const float*)" remove="all"/>
<modify-function signature="copyDataTo(float*) const" remove="all"/>
<modify-function signature="constData()const" remove="all"/>
</add-function>
</value-type>
- <value-type name="QMatrix3x4" since="4.6">
+ <value-type name="QMatrix3x4">
<modify-function signature="QMatrix3x4(const float*)" remove="all"/>
<modify-function signature="copyDataTo(float*) const" remove="all"/>
<modify-function signature="constData()const" remove="all"/>
</add-function>
</value-type>
- <value-type name="QMatrix4x2" since="4.6">
+ <value-type name="QMatrix4x2">
<modify-function signature="QMatrix4x2(const float*)" remove="all"/>
<modify-function signature="copyDataTo(float*) const" remove="all"/>
<modify-function signature="constData()const" remove="all"/>
</add-function>
</value-type>
- <value-type name="QMatrix4x3" since="4.6">
+ <value-type name="QMatrix4x3">
<modify-function signature="QMatrix4x3(const float*)" remove="all"/>
<modify-function signature="copyDataTo(float*) const" remove="all"/>
<modify-function signature="constData()const" remove="all"/>
</add-function>
</value-type>
- <value-type name="QMatrix4x4" since="4.6">
+ <value-type name="QMatrix4x4">
<enum-type name="Flag" flags="Flags"/>
<!-- Qt5: HAIRY TRICK ALERT ahead!
_______ end of matrix block _______ -->
- <value-type name="QQuaternion" since="4.6">
+ <value-type name="QQuaternion">
<add-function signature="__repr__" return-type="str">
<inject-code class="target" position="beginning">
<insert-template name="repr_code">
</modify-function>
</value-type>
- <object-type name="QTouchEvent" since="4.6">
+ <object-type name="QTouchEvent">
<add-function signature="__repr__" return-type="str">
<inject-code class="target" position="beginning">
<insert-template name="repr_qdebug_gui"/>
<enum-type name="DeviceType" flags="DeviceTypes"/>
</object-type>
- <value-type name="QVector2D" since="4.6">
+ <value-type name="QVector2D">
<add-function signature="__repr__" return-type="str">
<inject-code class="target" position="beginning">
<insert-template name="repr_code">
</add-function>
</value-type>
- <value-type name="QVector3D" since="4.6">
+ <value-type name="QVector3D">
<extra-includes>
<include file-name="QMatrix4x4" location="global"/>
</extra-includes>
</add-function>
</value-type>
- <value-type name="QVector4D" since="4.6">
+ <value-type name="QVector4D">
<extra-includes>
<include file-name="QMatrix4x4" location="global"/>
</extra-includes>
<insert-template name="return_native_eventfilter_conversion"/>
</conversion-rule>
</modify-argument>
- <inject-code position="end">
- <insert-template name="return_native_eventfilter"/>
- </inject-code>
+ <inject-code position="end"
+ file="../glue/qtcore.cpp" snippet="return-native-eventfilter"/>
</modify-function>
<modify-function signature="fromWinId(WId)">
<modify-argument index="1">
<add-function signature="exec_()" return-type="int">
<inject-code file="../glue/qtgui.cpp" snippet="qguiapplication-exec"/>
</add-function>
+ <modify-function signature="modalWindow()">
+ <modify-argument index="return" pyi-type="Optional[PySide6.QtGui.QWindow]"/>
+ </modify-function>
<add-function signature="nativeInterface()const" return-type="PyObject">
<modify-argument index="return"> <!-- Suppress return value heuristics -->
<define-ownership class="target" owner="default"/>
<inject-code class="target" position="beginning" file="../glue/qtgui.cpp"
snippet="qguiapplication-nativeInterface"/>
</add-function>
+ <modify-function signature="screenAt(const QPoint &)">
+ <modify-argument index="return" pyi-type="Optional[PySide6.QtGui.QScreen]"/>
+ </modify-function>
<modify-function signature="setOverrideCursor(const QCursor&)">
<modify-argument index="return" pyi-type="PyObject">
<replace-type modified-type="QtGuiHelper::QOverrideCursorGuard*"/>
<modify-argument index="return">
<replace-type modified-type="QString"/>
</modify-argument>
- <inject-code class="target" position="beginning">
- <insert-template name="glGetString_return_QString"/>
- </inject-code>
+ <inject-code class="target" position="end" file="../glue/qtgui.cpp"
+ snippet="glgetstring-return"/>
</modify-function>
<modify-function signature="glShaderBinary(GLint,const GLuint*,GLenum,const void*,GLint)">
<modify-argument index="2"><array/></modify-argument>
<object-type name="QMediaCaptureSession" since="6.1"/>
- <object-type name="QMediaDevices" since="6.1"/>
+ <!-- FFMPEG query functions need threads. -->
+ <object-type name="QMediaDevices" since="6.1" allow-thread="true"/>
<value-type name="QMediaFormat" since="6.1">
<enum-type name="AudioCodec"/>
<enum-type name="NetworkLayerProtocol"/>
<enum-type name="PauseMode" flags="PauseModes"/>
<enum-type name="SocketError"/>
- <enum-type name="SocketOption" since="4.6"/>
+ <enum-type name="SocketOption"/>
<enum-type name="SocketState"/>
<enum-type name="SocketType"/>
<modify-function signature="connectToHost(const QString&,quint16,QFlags<QIODeviceBase::OpenModeFlag>,QAbstractSocket::NetworkLayerProtocol)" allow-thread="yes"/>
<value-type name="QNetworkAddressEntry">
<enum-type name="DnsEligibilityStatus"/>
- <configuration condition="#ifndef QT_NO_NETWORKINTERFACE"/>
+ <configuration condition="#if QT_CONFIG(networkinterface)"/>
</value-type>
<value-type name="QNetworkInterface">
<enum-type name="InterfaceFlag" flags="InterfaceFlags"/>
<enum-type name="InterfaceType"/>
- <configuration condition="#ifndef QT_NO_NETWORKINTERFACE"/>
+ <configuration condition="#if QT_CONFIG(networkinterface)"/>
</value-type>
<value-type name="QNetworkProxy">
${QtNetworkAuth_GEN_DIR}/qoauth1_wrapper.cpp
${QtNetworkAuth_GEN_DIR}/qoauth1signature_wrapper.cpp
${QtNetworkAuth_GEN_DIR}/qoauth2authorizationcodeflow_wrapper.cpp
+${QtNetworkAuth_GEN_DIR}/qoauth2deviceauthorizationflow_wrapper.cpp
${QtNetworkAuth_GEN_DIR}/qoauthhttpserverreplyhandler_wrapper.cpp
${QtNetworkAuth_GEN_DIR}/qoauthoobreplyhandler_wrapper.cpp
${QtNetworkAuth_GEN_DIR}/qoauthurischemereplyhandler_wrapper.cpp
</add-function>
</object-type>
<object-type name="QAbstractOAuth2">
+ <enum-type name="NonceMode" since="6.9"/>
<modify-function signature="head(const QUrl&,const QMap<QString,QVariant>&)">
<modify-argument index="return">
<define-ownership class="target" owner="default"/>
</modify-argument>
</modify-function>
</object-type>
+ <object-type name="QOAuth2DeviceAuthorizationFlow" since="6.9"/>
<object-type name="QAbstractOAuthReplyHandler"/>
<object-type name="QOAuth1">
<enum-type name="SignatureMethod"/>
namespace-begin="QT_BEGIN_NAMESPACE" namespace-end="QT_END_NAMESPACE">
<load-typesystem name="QtCore/typesystem_core.xml" generate="no" />
<load-typesystem name="QtGui/typesystem_gui.xml" generate="no" />
- <load-typesystem name="templates/opengl_common.xml" generate="no" />
<rejection class="^QOpenGL.*$" argument-type="^const GLboolean ?\*$"/>
<rejection class="^QOpenGL.*$" argument-type="^GLchar\*$"/>
<modify-argument index="return">
<replace-type modified-type="QString"/>
</modify-argument>
- <inject-code class="target" position="beginning">
- <insert-template name="glGetString_return_QString"/>
- </inject-code>
+ <inject-code class="target" position="end" file="../glue/qtgui.cpp" snippet="glgetstring-return"/>
</modify-function>
<modify-function signature="^glTexParameterI?u?[fi]v\(.*$">
<modify-argument index="3"><array/></modify-argument>
<enum-type name="PrintEnginePropertyKey"/>
</object-type>
<value-type name="QPrinterInfo"/>
- <rejection class="QPrinter" function-name="printerSelectionOption"/>
- <rejection class="QPrinter" function-name="setPrinterSelectionOption"/>
<object-type name="QPrinter" >
<enum-type name="ColorMode"/>
<!-- Suppress anonymous enum warning -->
<suppress-warning text="Anonymous enum (QmlCurrentSingletonTypeRegistrationVersion) does not have a type entry"/>
<suppress-warning text="Enum 'QQmlModuleImportSpecialVersions' does not have a type entry"/>
+ <suppress-warning text="^Unable to decide type of property: .*QQmlListProperty.*Unable to translate type.*QQmlListProperty.*Cannot find type entry for.*QQmlListProperty.*$"/>
</typesystem>
<suppress-warning text="^signature.*fromVulkanImage.*not found.*$"/>
<suppress-warning text="^signature.*fromPhysicalDevice.*not found.*$"/>
<suppress-warning text="^signature.*fromDeviceObjects.*not found.*$"/>
+ <suppress-warning text="^Unable to decide type of property:.*QQuickGradient.*Unable to translate type.*QQuickGradient.*Cannot find type entry for.*QQuickGradient.*$"/>
</typesystem>
${QtRemoteObjects_GEN_DIR}/qtremoteobjects_module_wrapper.cpp
)
+find_package(Qt6 REQUIRED COMPONENTS Core)
+
set(QtRemoteObjects_include_dirs ${QtRemoteObjects_SOURCE_DIR}
${QtRemoteObjects_BINARY_DIR}
${Qt${QT_MAJOR_VERSION}RemoteObjects_INCLUDE_DIRS}
+ ${libpysideremoteobjects_SOURCE_DIR}
${SHIBOKEN_INCLUDE_DIR}
${libpyside_SOURCE_DIR}
${SHIBOKEN_PYTHON_INCLUDE_DIR}
${QtCore_GEN_DIR}
${QtNetwork_GEN_DIR})
-set(QtRemoteObjects_libraries pyside6
- ${Qt${QT_MAJOR_VERSION}RemoteObjects_LIBRARIES})
-
set(QtRemoteObjects_deps QtCore QtNetwork)
+set(QtRemoteObjects_libraries pyside6 pyside6remoteobjects
+ ${Qt${QT_MAJOR_VERSION}RemoteObjects_LIBRARIES})
+
create_pyside_module(NAME QtRemoteObjects
INCLUDE_DIRS QtRemoteObjects_include_dirs
LIBRARIES QtRemoteObjects_libraries
<load-typesystem name="templates/core_common.xml" generate="no"/>
<load-typesystem name="QtCore/typesystem_core.xml" generate="no"/>
<load-typesystem name="QtNetwork/typesystem_network.xml" generate="no"/>
+ <inject-code class="native" position="beginning">
+ #include "pysideremoteobjects.h"
+ </inject-code>
<rejection class="QRemoteObjectStringLiterals"/>
<rejection class="*" function-name="getTypeNameAndMetaobjectFromClassInfo"/>
</object-type>
<object-type name="QRemoteObjectNode">
<enum-type name="ErrorCode"/>
+ <add-function signature="acquire(PyTypeObject*, PyObject* @name@ = 0)"
+ return-type="PyTypeObject*">
+ <inject-code class="target" file="../glue/qtremoteobjects.cpp" snippet="node-acquire"/>
+ </add-function>
</object-type>
<object-type name="QRemoteObjectPendingCall">
<enum-type name="Error"/>
<object-type name="QRemoteObjectRegistryHost"/>
<object-type name="QRemoteObjectReplica">
<enum-type name="State"/>
- <!-- protected: <enum-type name="ConstructorType"/> -->
+ <enum-type name="ConstructorType" python-type="IntEnum"/> <!-- Needed even though protected -->
+ <modify-function signature="QRemoteObjectReplica(QRemoteObjectReplica::ConstructorType)">
+ <modify-argument index="1">
+ <replace-default-expression with="{}"/>
+ </modify-argument>
+ </modify-function>
</object-type>
<object-type name="QRemoteObjectSettingsStore"/>
<value-type name="QRemoteObjectSourceLocationInfo"/>
<!-- QtNetwork is pulled in via QtRemoteObjectsDepends. -->
<suppress-warning text="^Scoped enum 'Q(Ocsp)|(Dtls).*' does not have a type entry.*$"/>
+ <inject-code class="target" position="end"
+ file="../glue/qtremoteobjects.cpp" snippet="qtro-init"/>
+
</typesystem>
<value-type name="TimeStamp"/>
</value-type>
<object-type name="QCanDbcFileParser">
+ <modify-function signature="parse(const QString&)" overload-number="0"/>
+ <modify-function signature="parse(const QStringList&)" overload-number="1"/>
<enum-type name="Error"/>
</object-type>
<object-type name="QCanFrameProcessor">
</object-type>
<namespace-type name="QTest">
- <!-- Qt5: private <enum-type name="AttributeIndex" since="4.6"/> -->
+ <!-- Qt5: private <enum-type name="AttributeIndex"/> -->
<enum-type name="KeyAction"/>
- <!-- Qt5: private <enum-type name="LogElementType" since="4.6"/> -->
+ <!-- Qt5: private <enum-type name="LogElementType"/> -->
<enum-type name="MouseAction"/>
- <enum-type name="QBenchmarkMetric" since="4.7"/>
+ <enum-type name="QBenchmarkMetric"/>
<enum-type name="TestFailMode"/>
<enum-type name="ComparisonOperation" since="6.4"/>
<extra-includes>
<include file-name="QtTest" location="global"/>
</extra-includes>
- <object-type name="PySideQTouchEventSequence" target-lang-name="QTouchEventSequence" since="4.6" >
+ <object-type name="PySideQTouchEventSequence" target-lang-name="QTouchEventSequence">
<modify-function signature="press(int,const QPoint&,QWidget*)">
<modify-argument index="return">
<define-ownership class="target" owner="default"/>
</modify-argument>
</modify-function>
</object-type>
- <modify-function signature="generateTouchEvent(QWidget*,QPointingDevice*,bool)" rename="touchEvent" since="4.6">
+ <modify-function signature="generateTouchEvent(QWidget*,QPointingDevice*,bool)" rename="touchEvent">
<modify-argument index="return">
<define-ownership class="target" owner="target"/>
</modify-argument>
</modify-function>
- <modify-function signature="generateTouchEvent(QWindow*,QPointingDevice*,bool)" rename="touchEvent" since="4.6">
+ <modify-function signature="generateTouchEvent(QWindow*,QPointingDevice*,bool)" rename="touchEvent">
<modify-argument index="return">
<define-ownership class="target" owner="target"/>
</modify-argument>
${QtWebEngineCore_GEN_DIR}/qwebenginepage_wrapper.cpp
${QtWebEngineCore_GEN_DIR}/qwebenginepermission_wrapper.cpp
${QtWebEngineCore_GEN_DIR}/qwebengineprofile_wrapper.cpp
+${QtWebEngineCore_GEN_DIR}/qwebengineprofilebuilder_wrapper.cpp
${QtWebEngineCore_GEN_DIR}/qwebenginequotarequest_wrapper.cpp
${QtWebEngineCore_GEN_DIR}/qwebengineregisterprotocolhandlerrequest_wrapper.cpp
${QtWebEngineCore_GEN_DIR}/qwebenginescript_wrapper.cpp
</add-function>
</object-type>
+ <object-type name="QWebEngineProfileBuilder" since="6.9"/>
+
<object-type name="QWebEngineNewWindowRequest">
<enum-type name="DestinationType"/>
</object-type>
project(QtWebEngineQuick)
set(QtWebEngineQuick_SRC
+${QtWebEngineQuick_GEN_DIR}/qquickwebenginedownloadrequest_wrapper.cpp
${QtWebEngineQuick_GEN_DIR}/qquickwebengineprofile_wrapper.cpp
${QtWebEngineQuick_GEN_DIR}/qtwebenginequick_wrapper.cpp
# module is always needed
<enum-type name="PersistentCookiesPolicy"/>
<enum-type name="PersistentPermissionsPolicy" since="6.8"/>
</object-type>
+ <object-type name="QQuickWebEngineDownloadRequest" since="6.9"/>
+
</typesystem>
enum 'QGraphicsPolygonItem::Type' does not have a type entry or is not an enum
"""
-->
- <rejection class="QMdi"/>
-
<function signature="qDrawShadeLine(QPainter*,int,int,int,int,const QPalette&,bool,int,int)"/>
<function signature="qDrawShadeLine(QPainter*,const QPoint,const QPoint,const QPalette&,bool,int,int)"/>
<function signature="qDrawShadeRect(QPainter*,int,int,int,int,const QPalette&,bool,int,int,const QBrush*)"/>
<enum-type name="Extension"/>
<enum-type name="GraphicsItemChange"/>
<enum-type name="GraphicsItemFlag" flags="GraphicsItemFlags"/>
- <enum-type name="PanelModality" since="4.6"/>
+ <enum-type name="PanelModality"/>
<inject-code class="target" position="end" file="../glue/qtwidgets.cpp" snippet="qgraphicsitem"/>
<modify-function signature="setParentItem(QGraphicsItem*)">
<modify-argument index="this">
<modify-function signature="inputMethodEvent(QInputMethodEvent*)">
<modify-argument index="1" invalidate-after-use="yes"/>
</modify-function>
- <modify-function signature="isBlockedByModalPanel(QGraphicsItem**)const" since="4.6">
+ <modify-function signature="isBlockedByModalPanel(QGraphicsItem**)const">
<modify-argument index="1">
<remove-argument/>
</modify-argument>
</modify-function>
<modify-function signature="setItemDelegateForColumn(int,QAbstractItemDelegate*)">
<modify-argument index="2">
- <reference-count action="set"/>
+ <reference-count action="add"/>
</modify-argument>
</modify-function>
<modify-function signature="setItemDelegateForRow(int,QAbstractItemDelegate*)">
<modify-argument index="2">
- <reference-count action="set"/>
+ <reference-count action="add"/>
</modify-argument>
</modify-function>
<modify-function signature="model()const">
<parent index="this" action="add"/>
</modify-argument>
</modify-function>
- <!-- this fuction is declared when not defined QT_NO_TOOLBA -->
+ <!-- this function is declared when not defined QT_NO_TOOLBAR -->
<modify-function signature="addToolBar(Qt::ToolBarArea,QToolBar*)">
<modify-argument index="2">
<parent index="this" action="add"/>
polymorphic-id-expression="%B->type() == QEvent::GraphicsSceneWheel"/>
<object-type name="QGestureEvent"
- polymorphic-id-expression="%B->type() == QEvent::Gesture || %B->type() == QEvent::GestureOverride" since="4.6">
+ polymorphic-id-expression="%B->type() == QEvent::Gesture || %B->type() == QEvent::GestureOverride">
<modify-function signature="activeGestures()const">
<modify-argument index="return">
<define-ownership owner="default"/>
<enum-type name="ControlElement" python-type="IntEnum"/>
<enum-type name="PixelMetric" python-type="IntEnum"/>
<enum-type name="PrimitiveElement" python-type="IntEnum"/>
- <enum-type name="RequestSoftwareInputPanel" since="4.6"/>
+ <enum-type name="RequestSoftwareInputPanel"/>
<enum-type name="StandardPixmap" python-type="IntEnum"/>
<enum-type name="StateFlag" flags="State"/>
<enum-type name="StyleHint" python-type="IntEnum"/>
<modify-argument index="3">
<replace-type modified-type="PySequence"/>
<conversion-rule class="native">
- <insert-template name="qgraphicsitem_pysequence"/>
+ <insert-template name="pysequence-to-c-array">
+ <replace from="%TYPE" to="QGraphicsItem*"/>
+ </insert-template>
</conversion-rule>
<conversion-rule class="target">
- <insert-template name="qgraphicsitem_pyobject"/>
+ <insert-template name="c-array-to-pysequence">
+ <replace from="%TYPE" to="QGraphicsItem*"/>
+ <replace from="%COUNT" to="numItems"/>
+ </insert-template>
</conversion-rule>
</modify-argument>
<enum-type name="InputMode"/>
<modify-function signature="getInt(QWidget*,const QString&,const QString&,int,int,int,int,bool*,QFlags<Qt::WindowType>)" allow-thread="yes">
+ <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
<modify-argument index="return" pyi-type="Tuple[int, bool]"/>
<modify-argument index="8">
<remove-default-expression/>
</modify-function>
<modify-function signature="getItem(QWidget*,const QString&,const QString&,const QStringList&,int,bool,bool*,QFlags<Qt::WindowType>,QFlags<Qt::InputMethodHint>)" allow-thread="yes">
+ <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
<modify-argument index="return" pyi-type="Tuple[str, bool]"/>
<modify-argument index="7">
<remove-default-expression/>
</modify-function>
<modify-function signature="getMultiLineText(QWidget*,const QString&,const QString&,const QString&,bool*,QFlags<Qt::WindowType>,QFlags<Qt::InputMethodHint>)" allow-thread="yes">
+ <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
<modify-argument index="return" pyi-type="Tuple[str, bool]"/>
<modify-argument index="5">
<remove-default-expression/>
</modify-function>
<modify-function signature="getText(QWidget*,const QString&,const QString&,QLineEdit::EchoMode,const QString&,bool*,QFlags<Qt::WindowType>,QFlags<Qt::InputMethodHint>)" allow-thread="yes">
+ <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
<modify-argument index="return" pyi-type="Tuple[str, bool]"/>
<modify-argument index="6">
<remove-default-expression/>
</modify-function>
<modify-function signature="getDouble(QWidget*,const QString&,const QString&,double,double,double,int,bool*,QFlags<Qt::WindowType>,double)" allow-thread="yes">
+ <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
<modify-argument index="return" pyi-type="Tuple[float, bool]"/>
<modify-argument index="8">
<remove-default-expression/>
</modify-argument>
</modify-function>
<modify-function signature="setItemWidget(QTreeWidgetItem*,int,QWidget*)" allow-thread="yes">
- <modify-argument index="3">
+ <modify-argument index="3" pyi-type="Optional[PySide6.QtWidgets.QWidget]">
<parent index="this" action="add"/>
</modify-argument>
</modify-function>
</modify-argument>
</modify-function>
<modify-function signature="setItemWidget(QListWidgetItem*,QWidget*)" allow-thread="yes">
- <modify-argument index="2">
+ <modify-argument index="2" pyi-type="Optional[PySide6.QtWidgets.QWidget]">
<parent index="1" action="add"/>
</modify-argument>
</modify-function>
<insert-template name="return_native_eventfilter_conversion"/>
</conversion-rule>
</modify-argument>
- <inject-code position="end">
- <insert-template name="return_native_eventfilter"/>
- </inject-code>
+ <inject-code position="end"
+ file="../glue/qtcore.cpp" snippet="return-native-eventfilter"/>
</modify-function>
<extra-includes>
snippet="qmessagebox-open-connect-accept"/>
</add-function>
<!-- FIXME PYSIDE-7: Remove deprecated overloads -->
- <modify-function signature="critical(QWidget*,const QString&,const QString&,QFlags<QMessageBox::StandardButton>,QMessageBox::StandardButton)" allow-thread="yes"/>
+ <modify-function signature="critical(QWidget*,const QString&,const QString&,QFlags<QMessageBox::StandardButton>,QMessageBox::StandardButton)" allow-thread="yes">
+ <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
+ </modify-function>
<modify-function signature="critical(QWidget*,const QString&,const QString&,QMessageBox::StandardButton,QMessageBox::StandardButton)"
- allow-thread="yes"/>
- <modify-function signature="information(QWidget*,const QString&,const QString&,QFlags<QMessageBox::StandardButton>,QMessageBox::StandardButton)" allow-thread="yes"/>
+ allow-thread="yes">
+ <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
+ </modify-function>
+ <modify-function signature="information(QWidget*,const QString&,const QString&,QFlags<QMessageBox::StandardButton>,QMessageBox::StandardButton)" allow-thread="yes">
+ <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
+ </modify-function>
<modify-function signature="information(QWidget*,const QString&,const QString&,QMessageBox::StandardButton,QMessageBox::StandardButton)"
- allow-thread="yes"/>
- <modify-function signature="question(QWidget*,const QString&,const QString&,QFlags<QMessageBox::StandardButton>,QMessageBox::StandardButton)" allow-thread="yes"/>
+ allow-thread="yes">
+ <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
+ </modify-function>
+ <modify-function signature="question(QWidget*,const QString&,const QString&,QFlags<QMessageBox::StandardButton>,QMessageBox::StandardButton)" allow-thread="yes">
+ <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
+ </modify-function>
<modify-function signature="question(QWidget*,const QString&,const QString&,QMessageBox::StandardButton,QMessageBox::StandardButton)"
- allow-thread="yes"/>
- <modify-function signature="warning(QWidget*,const QString&,const QString&,QFlags<QMessageBox::StandardButton>,QMessageBox::StandardButton)" allow-thread="yes"/>
+ allow-thread="yes">
+ <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
+ </modify-function>
+ <modify-function signature="warning(QWidget*,const QString&,const QString&,QFlags<QMessageBox::StandardButton>,QMessageBox::StandardButton)" allow-thread="yes">
+ <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
+ </modify-function>
<modify-function signature="warning(QWidget*,const QString&,const QString&,QMessageBox::StandardButton,QMessageBox::StandardButton)"
- allow-thread="yes"/>
+ allow-thread="yes">
+ <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
+ </modify-function>
<modify-function signature="QMessageBox(const QString&,const QString&,QMessageBox::Icon,int,int,int,QWidget*,QFlags<Qt::WindowType>)" remove="all"/>
<modify-function signature="critical(QWidget*,const QString&,const QString&,int,int,int)" remove="all"/>
<modify-function signature="critical(QWidget*,const QString&,const QString&,const QString&,const QString&,const QString&,int,int)" remove="all"/>
<modify-function signature="question(QWidget*,const QString&,const QString&,const QString&,const QString&,const QString&,int,int)" remove="all"/>
<modify-function signature="warning(QWidget*,const QString&,const QString&,int,int,int)" remove="all"/>
<modify-function signature="warning(QWidget*,const QString&,const QString&,const QString&,const QString&,const QString&,int,int)" remove="all"/>
- <modify-function signature="about(QWidget*,const QString&,const QString&)" allow-thread="yes"/>
- <modify-function signature="aboutQt(QWidget*,const QString&)" allow-thread="yes"/>
+ <modify-function signature="about(QWidget*,const QString&,const QString&)" allow-thread="yes">
+ <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
+ </modify-function>
+ <modify-function signature="aboutQt(QWidget*,const QString&)" allow-thread="yes">
+ <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QWidget]"/>
+ </modify-function>
</object-type>
<object-type name="QAbstractSpinBox">
<enum-type name="ButtonSymbols"/>
<modify-argument index="return">
<replace-type modified-type="QString"/>
</modify-argument>
+ <inject-code class="native" position="end">
+ <insert-template name="return_QString_native"/>
+ </inject-code>
<inject-code class="target" position="end" file="../glue/qtcore.cpp" snippet="qstring-return"/>
</modify-function>
<modify-function signature="validate(QString &,int &)const">
</modify-argument>
</modify-function>
<modify-function signature="setCancelButton(QPushButton*)">
- <modify-argument index="1">
+ <modify-argument index="1" pyi-type="Optional[PySide6.QtWidgets.QPushButton]">
<parent index="this" action="add"/>
</modify-argument>
</modify-function>
<enum-type name="Shape"/>
<enum-type name="SelectionBehavior"/>
<enum-type name="ButtonPosition"/>
+ <modify-function signature="setTabButton(int,QTabBar::ButtonPosition,QWidget*)">
+ <modify-argument index="3" pyi-type="Optional[PySide6.QtWidgets.QWidget]">
+ <parent index="this" action="add"/>
+ </modify-argument>
+ </modify-function>
</object-type>
<object-type name="QRadioButton"/>
<object-type name="QScrollBar"/>
<parent index="this" action="add"/>
</modify-argument>
</modify-function>
+ <modify-function signature="setText(const QString&)">
+ <modify-argument index="1" pyi-type="Optional[str]"/>
+ </modify-function>
<modify-function signature="setValidator(const QValidator*)">
<modify-argument index="1">
<parent index="this" action="add"/>
</object-type>
<object-type name="QComboBox">
<enum-type name="InsertPolicy"/>
+ <enum-type name="LabelDrawingMode" since="6.9"/>
<enum-type name="SizeAdjustPolicy"/>
<modify-function signature="setCompleter(QCompleter*)">
<modify-argument index="1">
</modify-function>
</object-type>
<object-type name="QGraphicsLayout">
- <modify-function signature="getContentsMargins(qreal*,qreal*,qreal*,qreal*)const">
- <modify-argument index="0">
- <replace-type modified-type="PyObject"/>
- </modify-argument>
- <modify-argument index="1">
- <remove-argument/>
- </modify-argument>
- <modify-argument index="2">
- <remove-argument/>
- </modify-argument>
- <modify-argument index="3">
- <remove-argument/>
- </modify-argument>
- <modify-argument index="4">
- <remove-argument/>
- </modify-argument>
- <inject-code class="target" position="beginning">
- <insert-template name="fix_number*,number*,number*,number*">
- <replace from="$TYPE" to="qreal"/>
- </insert-template>
- </inject-code>
- <inject-code class="native" position="end">
- <insert-template name="fix_native_return_number*,number*,number*,number*">
- <replace from="$TYPE" to="qreal"/>
- </insert-template>
- </inject-code>
- </modify-function>
<modify-function signature="widgetEvent(QEvent*)">
<modify-argument index="1" invalidate-after-use="yes"/>
</modify-function>
</object-type>
<!-- a QObject so main-thread delete redundant -->
<object-type name="QGraphicsWidget">
- <modify-function signature="getContentsMargins(qreal*,qreal*,qreal*,qreal*)const">
- <modify-argument index="return">
- <replace-type modified-type="PyObject"/>
- </modify-argument>
- <modify-argument index="1">
- <remove-argument/>
- </modify-argument>
- <modify-argument index="2">
- <remove-argument/>
- </modify-argument>
- <modify-argument index="3">
- <remove-argument/>
- </modify-argument>
- <modify-argument index="4">
- <remove-argument/>
- </modify-argument>
-
- <inject-code class="target" position="beginning">
- <insert-template name="fix_number*,number*,number*,number*">
- <replace from="$TYPE" to="qreal"/>
- </insert-template>
- </inject-code>
- </modify-function>
<modify-function signature="getWindowFrameMargins(qreal*,qreal*,qreal*,qreal*)const">
<modify-argument index="return">
<replace-type modified-type="PyObject"/>
</modify-function>
</object-type>
- <object-type name="QGesture" since="4.6">
+ <object-type name="QGesture">
<enum-type name="GestureCancelPolicy"/>
</object-type>
- <object-type name="QGestureRecognizer" since="4.6">
+ <object-type name="QGestureRecognizer">
<enum-type name="ResultFlag" flags="Result"/>
<modify-function signature="create(QObject*)">
<modify-argument index="return">
</modify-argument>
</modify-function>
</object-type>
- <object-type name="QTapAndHoldGesture" since="4.6"/>
- <object-type name="QTapGesture" since="4.6"/>
- <object-type name="QGraphicsAnchor" since="4.6"/>
- <object-type name="QGraphicsAnchorLayout" since="4.6"/>
- <object-type name="QGraphicsBlurEffect" since="4.6">
+ <object-type name="QTapAndHoldGesture"/>
+ <object-type name="QTapGesture"/>
+ <object-type name="QGraphicsAnchor"/>
+ <object-type name="QGraphicsAnchorLayout"/>
+ <object-type name="QGraphicsBlurEffect">
<enum-type name="BlurHint" flags="BlurHints"/>
</object-type>
- <object-type name="QGraphicsColorizeEffect" since="4.6"/>
- <object-type name="QGraphicsDropShadowEffect" since="4.6"/>
+ <object-type name="QGraphicsColorizeEffect"/>
+ <object-type name="QGraphicsDropShadowEffect"/>
- <object-type name="QGraphicsEffect" since="4.6">
+ <object-type name="QGraphicsEffect">
<enum-type name="ChangeFlag" flags="ChangeFlags"/>
<enum-type name="PixmapPadMode"/>
</object-type>
- <object-type name="QGraphicsObject" since="4.6" default-superclass="QGraphicsItem"/>
- <object-type name="QGraphicsOpacityEffect" since="4.6"/>
- <object-type name="QGraphicsRotation" since="4.6"/>
- <object-type name="QGraphicsScale" since="4.6"/>
- <object-type name="QGraphicsTransform" since="4.6"/>
- <object-type name="QPanGesture" since="4.6"/>
- <object-type name="QPinchGesture" since="4.6">
+ <object-type name="QGraphicsObject"/>
+ <object-type name="QGraphicsOpacityEffect"/>
+ <object-type name="QGraphicsRotation"/>
+ <object-type name="QGraphicsScale"/>
+ <object-type name="QGraphicsTransform"/>
+ <object-type name="QPanGesture"/>
+ <object-type name="QPinchGesture">
<enum-type name="ChangeFlag" flags="ChangeFlags"/>
</object-type>
<enum-type name="TextureFormat"/>
</object-type>
- <object-type name="QSwipeGesture" since="4.6">
+ <object-type name="QSwipeGesture">
<enum-type name="SwipeDirection"/>
</object-type>
- <value-type name="QTileRules" since="4.6"/>
+ <value-type name="QTileRules"/>
<object-type name="QScroller">
<enum-type name="State"/>
# Instead, we use __getattr__ which is supported since Python 3.7
# and create the __all__ list on demand when needed.
- location = Path(__file__).resolve().parent
- files = os.listdir(location)
- unordered = set(name[: name.find(".")] for name in files if name.startswith("Qt") and (
- name.endswith((".pyd", ".so"))))
+ unordered = set()
+ pattern = "Qt*.pyd" if sys.platform == "win32" else "Qt*.so"
+ for module in Path(__file__).resolve().parent.glob(pattern):
+ name = module.name[:module.name.find(".")]
+ if name.endswith("_d"): # Windows debug suffix?
+ name = name[:-2]
+ unordered.add(name)
ordered_part = __pre_all__
result = []
for name in ordered_part:
"@all_module_shortnames@"
.split(";"))
-shiboken_library_soversion = str(@SHIBOKEN_SO_VERSION@)
-pyside_library_soversion = str(@PYSIDE_SO_VERSION@)
+shiboken_library_soversion = "@SHIBOKEN_SO_VERSION@"
+pyside_library_soversion = "@PYSIDE_SO_VERSION@"
version = "@FINAL_PACKAGE_VERSION@"
version_info = (@BINDING_API_MAJOR_VERSION@, @BINDING_API_MINOR_VERSION@, @BINDING_API_MICRO_VERSION@, "@BINDING_API_PRE_RELEASE_VERSION_TYPE@", "@BINDING_API_PRE_RELEASE_VERSION@")
settings.value('var', type=list) # Will get ["a"]
// @snippet qsettings-value
+
+// @snippet qmessagelogger
+
+In Python, the :class:`QMessageLogger` is useful to connect an existing logging
+setup that uses the Python logging module to the Qt logging system. This allows
+you to leverage Qt's logging infrastructure while still using the familiar
+Python logging API.
+
+Example::
+
+ import logging
+ from PySide6.QtCore import QMessageLogger
+
+ class LogHandler(logging.Handler):
+ def emit(self, record: logging.LogRecord):
+ if record.levelno == logging.DEBUG:
+ logger = QMessageLogger(record.filename, record.lineno, record.funcName)
+ logger.debug(record.message)
+
+ logging.basicConfig(handlers=[LogHandler()])
+ logging.debug("Test debug message")
+
+// @snippet qmessagelogger
singleton class. The lookup via qmlTypeId() is costly.
// @snippet qqmlengine-singletoninstance-qmltypeid
-// @snippet qqmlengine-singletoninstance-typename Returns the instance of a
-singleton type named typeName from the module specified by uri.
-For ``QObject``-derived singleton types, the ``QObject`` instance is returned,
-otherwise a ``QJSValue`` or ``None``.
+// @snippet qqmlengine-singletoninstance-typename
+Returns the instance of a singleton type named typeName from the module specified
+by uri. For ``QObject``-derived singleton types, the ``QObject`` instance is
+returned, otherwise a ``QJSValue`` or ``None``.
This method can be used as an alternative to calling qmlTypeId followed by the
id based overload of singletonInstance. This is convenient when one only needs
// Convert any string, etc, to a list of 1 element
if (auto *primitiveValue = convertToPrimitiveType(out, out.typeId())) {
PyObject *list = PyList_New(1);
- PyList_SET_ITEM(list, 0, primitiveValue);
+ PyList_SetItem(list, 0, primitiveValue);
return list;
}
PyObject *list = PyList_New(valuesSize);
for (Py_ssize_t i = 0; i < valuesSize; ++i) {
PyObject *item = PyUnicode_FromString(valuesList.at(i).constData());
- PyList_SET_ITEM(list, i, item);
+ PyList_SetItem(list, i, item);
}
return list;
}
static void msgHandlerCallback(QtMsgType type, const QMessageLogContext &ctx, const QString &msg)
{
Shiboken::GilState state;
+ PyObject *excType{};
+ PyObject *excValue{};
+ PyObject *excTraceback{};
+ PyErr_Fetch(&excType, &excValue, &excTraceback);
Shiboken::AutoDecRef arglist(PyTuple_New(3));
PyTuple_SetItem(arglist, 0, %CONVERTTOPYTHON[QtMsgType](type));
PyTuple_SetItem(arglist, 1, %CONVERTTOPYTHON[QMessageLogContext &](ctx));
const char *data = array.constData();
PyTuple_SetItem(arglist, 2, %CONVERTTOPYTHON[const char *](data));
Shiboken::AutoDecRef ret(PyObject_CallObject(qtmsghandler, arglist));
+ PyErr_Restore(excType, excValue, excTraceback);
}
// @snippet qt-messagehandler
Py_END_ALLOW_THREADS
// @snippet qdebug-format-string
+// @snippet qmessagelogger-format-string
+Py_BEGIN_ALLOW_THREADS
+%CPPSELF->%FUNCTION_NAME("%s", %1); // Uses placeholder for security reasons
+Py_END_ALLOW_THREADS
+// @snippet qmessagelogger-format-string
+
+// @snippet qmessagelogger-logcategory-format-string
+Py_BEGIN_ALLOW_THREADS
+%CPPSELF->%FUNCTION_NAME(%1, "%s", %2); // Uses placeholder for security reasons
+Py_END_ALLOW_THREADS
+// @snippet qmessagelogger-logcategory-format-string
+
// @snippet qresource-registerResource
auto ptr = reinterpret_cast<uchar *>(Shiboken::Buffer::getPointer(%PYARG_1));
%RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(const_cast<const uchar *>(ptr), %2);
return PyBytes_FromStringAndSize(%in.constData(), %in.size());
// @snippet return-pybytes
+// @snippet chrono-to-pylong
+return PyLong_FromLong(%in.count());
+// @snippet chrono-to-pylong
+
+// @snippet pylong-to-chrono
+%out = %OUTTYPE(PyLong_AsLongLong(%in));
+// @snippet pylong-to-chrono
+
// @snippet return-pylong
return PyLong_FromLong(%in);
// @snippet return-pylong
#endif
// @snippet qthread_pthread_cleanup_uninstall
-// @snippet qlibraryinfo_build
-auto oldResult = pyResult;
-const auto version = _PepRuntimeVersion();
-pyResult = PyUnicode_FromFormat(
-#ifdef Py_LIMITED_API
- "%U [Python limited API %d.%d.%d]",
+// @snippet qlibraryinfo_python_build
+
+// For versions with one byte per digit.
+static QByteArray versionString(long version)
+{
+ return QByteArray::number((version >> 16) & 0xFF)
+ + '.' + QByteArray::number((version >> 8) & 0xFF)
+ + '.' + QByteArray::number(version & 0xFF);
+}
+
+static QByteArray pythonBuild()
+{
+ using namespace Qt::StringLiterals;
+
+#ifdef PYPY_VERSION
+ QByteArray result = "PyPy " PYPY_VERSION
#else
- "%U [Python %d.%d.%d]",
+ QByteArray result = "Python"
+#endif
+#ifdef Py_LIMITED_API
+ " limited API"
+#endif
+#ifdef Py_GIL_DISABLED
+ " free threaded"
#endif
- oldResult, (version >> 16) & 0xFF,
- (version >> 8) & 0xFF, version & 0xFF);
-Py_DECREF(oldResult);
+ ;
+ result += ' ';
+
+ const auto runTimeVersion = _PepRuntimeVersion();
+ const auto runTimeVersionB = versionString(runTimeVersion);
+ constexpr long buildVersion = PY_VERSION_HEX >> 8;
+ if (runTimeVersion == buildVersion) {
+ result += runTimeVersionB;
+ } else {
+ result += "run time: "_ba + runTimeVersionB + " built: "_ba
+ + versionString(buildVersion);
+ }
+ return result;
+}
+// @snippet qlibraryinfo_python_build
+
+// @snippet qlibraryinfo_build
+QByteArray %0 = %CPPSELF.%FUNCTION_NAME();
+%0 += " [" + pythonBuild() + ']';
+%PYARG_0 = PyUnicode_FromString(%0.constData());
// @snippet qlibraryinfo_build
// @snippet qsharedmemory_data_readonly
+ %CPPSELF.absoluteFilePath().toUtf8() + "\")>";
%PYARG_0 = Shiboken::String::fromCString(result.constData());
// @snippet qdirlisting-direntry-repr
+
+// @snippet return-native-eventfilter-conversion
+%RETURN_TYPE %out = false;
+if (PySequence_Check(%PYARG_0) != 0 && PySequence_Size(%PYARG_0) == 2) {
+ Shiboken::AutoDecRef pyItem(PySequence_GetItem(%PYARG_0, 0));
+ %out = %CONVERTTOCPP[bool](pyItem);
+ if (result) {
+ Shiboken::AutoDecRef pyResultItem(PySequence_GetItem(pyResult, 1));
+ *result = %CONVERTTOCPP[qintptr](pyResultItem);
+ }
+}
+// @snippet return-native-eventfilter-conversion
+
+// @snippet return-native-eventfilter
+%PYARG_0 = PyTuple_New(2);
+PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](%0));
+PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[qintptr](*result_out));
+// @snippet return-native-eventfilter
int x, y;
%CPPSELF.point(i, &x, &y);
QPoint pt{x, y};
- PyList_SET_ITEM(points, i, %CONVERTTOPYTHON[QPoint](pt));
+ PyList_SetItem(points, i, %CONVERTTOPYTHON[QPoint](pt));
}
// @snippet qpolygon-reduce
%0 = new %TYPE(QPixmap::fromImage(%1));
// @snippet qpixmap
+// @snippet qpixmap-load-xpm
+Shiboken::AutoDecRef strList(PySequence_Fast(%PYARG_1, "Invalid sequence."));
+Py_ssize_t lineCount = PySequence_Size(strList.object());
+for (Py_ssize_t line = 0; line < lineCount; ++line) {
+ Shiboken::AutoDecRef _obj(PySequence_GetItem(strList.object(), line));
+ if (!Shiboken::String::check(_obj)) {
+ PyErr_SetString(PyExc_TypeError, "The argument must be a sequence of strings.");
+ break;
+ }
+}
+// PySIDE-1735: Enums are now implemented in Python, so we need to avoid asserts.
+if (PyErr_Occurred())
+ break;
+
+Shiboken::ArrayPointer<const char*> xpm(lineCount);
+for (Py_ssize_t line = 0; line < lineCount; ++line) {
+ Shiboken::AutoDecRef _obj(PySequence_GetItem(strList.object(), line));
+ xpm[line] = Shiboken::String::toCString(_obj);
+}
+
+%0 = new %TYPE(xpm);
+// @snippet qpixmap-load-xpm
+
// @snippet qicon-addpixmap
const auto path = PySide::pyPathToQString(%PYARG_1);
%CPPSELF->addPixmap(path);
%CPPSELF->setImage(QImage(path));
// @snippet qclipboard-setimage
+// @snippet qimage-buffer-constructor
+Py_INCREF(%PYARG_1);
+auto *ptr = reinterpret_cast<uchar *>(Shiboken::Buffer::getPointer(%PYARG_1));
+%0 = new %TYPE(ptr, %ARGS, imageDecrefDataHandler, %PYARG_1);
+// @snippet qimage-buffer-constructor
+
// @snippet qimage-decref-image-data
static void imageDecrefDataHandler(void *data)
{
}
// @snippet qcolor-totuple
+// @snippet qcolor-repr
+QString repr;
+switch (%CPPSELF.spec()) {
+case QColor::Rgb: {
+ float r, g, b, a;
+ %CPPSELF.getRgbF(&r, &g, &b, &a);
+ repr = QString::asprintf("PySide6.QtGui.QColor.fromRgbF(%.6f, %.6f, %.6f, %.6f)",
+ r, g, b, a);
+ break;
+}
+case QColor::Hsv: {
+ float h, s, v, a;
+ %CPPSELF.getHsvF(&h, &s, &v, &a);
+ repr = QString::asprintf("PySide6.QtGui.QColor.fromHsvF(%.6f, %.6f, %.6f, %.6f)",
+ h, s, v, a);
+ break;
+}
+case QColor::Cmyk: {
+ float c, m, y, k, a;
+ %CPPSELF.getCmykF(&c, &m, &y, &k, &a);
+ repr = QString::asprintf("PySide6.QtGui.QColor.fromCmykF(%.6f, %.6f, %.6f, %.6f, %.6f)",
+ c, m, y, k, a);
+ break;
+}
+case QColor::Hsl: {
+ float h, s, l, a;
+ %CPPSELF.getHslF(&h, &s, &l, &a);
+ repr = QString::asprintf("PySide6.QtGui.QColor.fromHslF(%.6f, %.6f, %.6f, %.6f)",
+ h, s, l, a);
+ break;
+}
+default:
+ repr = QLatin1StringView("PySide6.QtGui.QColor()");
+ break;
+}
+%PYARG_0 = Shiboken::String::fromCString(qPrintable(repr));
+// @snippet qcolor-repr
+
// @snippet qcolor
if (%1.type() == QVariant::Color)
%0 = new %TYPE(%1.value<QColor>());
PyErr_SetString(PyExc_TypeError, "QVariant must be holding a QColor");
// @snippet qcolor
+// @snippet qfont-tag-from-str-helper
+using FontTagOptional = std::optional<QFont::Tag>;
+static std::optional<QFont::Tag> qFontTagFromString(PyObject *unicode)
+{
+ FontTagOptional result;
+ if (PyUnicode_GetLength(unicode) == 4)
+ result = QFont::Tag::fromString(PySide::pyUnicodeToQString(unicode));
+ if (!result.has_value())
+ PyErr_SetString(PyExc_TypeError,
+ "QFont::Tag(): The tag name must be exactly 4 characters long.");
+ return result;
+}
+// @snippet qfont-tag-from-str-helper
+
+// @snippet qfont-tag-init-str
+const FontTagOptional tagO = qFontTagFromString(%PYARG_1);
+if (tagO.has_value())
+ %0 = new QFont::Tag(tagO.value());
+// @snippet qfont-tag-init-str
+
+// @snippet qfont-tag-fromString
+const FontTagOptional tagO = qFontTagFromString(%PYARG_1);
+if (tagO.has_value()) {
+ const auto &tag = tagO.value();
+ %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](tag);
+}
+// @snippet qfont-tag-fromString
+
+// @snippet qfont-tag-fromValue
+const FontTagOptional tagO = QFont::Tag::fromValue(PyLong_AsUnsignedLong(%PYARG_1));
+if (tagO.has_value()) {
+ const auto &tag = tagO.value();
+ %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](tag);
+} else {
+ PyErr_SetString(PyExc_TypeError, "QFont::Tag::fromValue(): Invalid value passed.");
+}
+// @snippet qfont-tag-fromValue
+
+// @snippet qfontmetrics-qfontcharfix
+if (Shiboken::String::len(%PYARG_1) == 1) {
+ const char *str = Shiboken::String::toCString(%PYARG_1);
+ const QChar ch(static_cast<unsigned short>(str[0]));
+ %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(ch);
+ %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
+} else {
+ PyErr_SetString(PyExc_TypeError, "String must have only one character");
+}
+// @snippet qfontmetrics-qfontcharfix
+
// @snippet qfontmetricsf-boundingrect
int *array = nullptr;
bool errorOccurred = false;
%CPPSELF.%FUNCTION_NAME(%1, %2.size(), %2.constData(), %3, %4, %5);
// @snippet qrhi-commandbuffer-setvertexinput
+// @snippet qpainterstateguard-restore
+%CPPSELF.restore();
+// @snippet qpainterstateguard-restore
+
+// @snippet qmatrix-repr-code
+QByteArray format(Py_TYPE(%PYSELF)->tp_name);
+format += QByteArrayLiteral("((");
+%MATRIX_TYPE data[%MATRIX_SIZE];
+%CPPSELF.copyDataTo(data);
+for (int i = 0; i < %MATRIX_SIZE; ++i) {
+ if (i > 0)
+ format += ", ";
+ format += QByteArray::number(data[i]);
+}
+format += "))";
+
+%PYARG_0 = Shiboken::String::fromStringAndSize(format, format.size());
+// @snippet qmatrix-repr-code
+
+// @snippet qmatrix-reduce-code
+%MATRIX_TYPE data[%MATRIX_SIZE];
+%CPPSELF.copyDataTo(data);
+QList<%MATRIX_TYPE> cppArgs(data, data + %MATRIX_SIZE);
+PyObject *type = PyObject_Type(%PYSELF);
+PyObject *args = Py_BuildValue("(N)",
+ %CONVERTTOPYTHON[QList<%MATRIX_TYPE>](cppArgs));
+%PYARG_0 = Py_BuildValue("(NN)", type, args);
+// @snippet qmatrix-reduce-code
+
+// @snippet qmatrix-data-function
+PyObject *pyData = PyTuple_New(%MATRIX_SIZE);
+if (const float *data = %CPPSELF.constData()) {
+ for (int i = 0; i < %MATRIX_SIZE; ++i)
+ PyTuple_SetItem(pyData, i, %CONVERTTOPYTHON[float](data[i]));
+}
+return pyData;
+// @snippet qmatrix-data-function
+
+// @snippet qmatrix-constructor
+// PYSIDE-795: All PySequences can be made iterable with PySequence_Fast.
+Shiboken::AutoDecRef seq(PySequence_Fast(%PYARG_1, "Can't turn into sequence"));
+if (PySequence_Size(seq) == %SIZE) {
+ Shiboken::AutoDecRef fast(PySequence_Fast(seq,
+ "Failed to parse sequence on %TYPE constructor."));
+ float values[%SIZE];
+ for (int i = 0; i < %SIZE; ++i) {
+ Shiboken::AutoDecRef pv(PySequence_GetItem(fast.object(), i));
+ values[i] = %CONVERTTOCPP[float](pv);
+ }
+ %0 = new %TYPE(values);
+}
+// @snippet qmatrix-constructor
+
+// @snippet validator-conversionrule
+QValidator::State %out;
+
+if (PySequence_Check(%PYARG_0)) {
+ Shiboken::AutoDecRef seq(PySequence_Fast(%PYARG_0, 0));
+ const Py_ssize_t size = PySequence_Size(seq.object());
+
+ if (size > 1) {
+ Shiboken::AutoDecRef _obj1(PySequence_GetItem(seq.object(), 1));
+ if (%ISCONVERTIBLE[QString](_obj1))
+ %1 = %CONVERTTOCPP[QString](_obj1);
+ else
+ qWarning("%TYPE::%FUNCTION_NAME: Second tuple element is not convertible to unicode.");
+ }
+
+ if (size > 2) {
+ Shiboken::AutoDecRef _obj2(PySequence_GetItem(seq.object(), 2));
+ if (%ISCONVERTIBLE[int](_obj2))
+ %2 = %CONVERTTOCPP[int](_obj2);
+ else
+ qWarning("%TYPE::%FUNCTION_NAME: Second tuple element is not convertible to int.");
+ }
+ Shiboken::AutoDecRef _sobj(PySequence_GetItem(seq.object(), 0));
+
+ %PYARG_0.reset(_sobj);
+ Py_INCREF(%PYARG_0); // we need to incref, because "%PYARG_0 = ..." will decref the tuple and the tuple will be decrefed again at the end of this scope.
+}
+
+// check return value
+if (%ISCONVERTIBLE[QValidator::State](%PYARG_0)) {
+ %out = %CONVERTTOCPP[QValidator::State](%PYARG_0);
+} else {
+ PyErr_Format(PyExc_TypeError, "Invalid return value in function %s, expected %s, got %s.",
+ "QValidator.validate",
+ "PySide6.QtGui.QValidator.State, (PySide6.QtGui.QValidator.State,), (PySide6.QtGui.QValidator.State, unicode) or (PySide6.QtGui.QValidator.State, unicode, int)",
+ Py_TYPE(pyResult)->tp_name);
+ return QValidator::State();
+}
+// @snippet validator-conversionrule
+
+// @snippet fix_margins_return
+PyObject *obj = %PYARG_0.object();
+bool ok = false;
+if (PySequence_Check(obj) != 0 && PySequence_Size(obj) == 4) {
+ Shiboken::AutoDecRef m0(PySequence_GetItem(obj, 0));
+ Shiboken::AutoDecRef m1(PySequence_GetItem(obj, 1));
+ Shiboken::AutoDecRef m2(PySequence_GetItem(obj, 2));
+ Shiboken::AutoDecRef m3(PySequence_GetItem(obj, 3));
+ ok = PyNumber_Check(m0) != 0 && PyNumber_Check(m1) != 0
+ && PyNumber_Check(m2) && PyNumber_Check(m3) != 0;
+ if (ok) {
+ *%1 = %CONVERTTOCPP[$TYPE](m0);
+ *%2 = %CONVERTTOCPP[$TYPE](m1);
+ *%3 = %CONVERTTOCPP[$TYPE](m2);
+ *%4 = %CONVERTTOCPP[$TYPE](m3);
+ }
+}
+if (!ok) {
+ PyErr_SetString(PyExc_TypeError, "Sequence of 4 numbers expected");
+ %1 = %2 = %3 = %4 = 0;
+}
+// @snippet fix_margins_return
+
/*********************************************************************
* CONVERSIONS
********************************************************************/
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// @snippet qudpsocket-readdatagram
-Shiboken::AutoArrayPointer<char> data(%ARGUMENT_NAMES);
+Shiboken::ArrayPointer<char> data(%ARGUMENT_NAMES);
QHostAddress ha;
quint16 port;
%BEGIN_ALLOW_THREADS
%PYARG_0 = PyList_New(vertexCount);
for (Py_ssize_t i = 0; i < vertexCount; ++i) {
QSGGeometry::Point2D p = points[i];
- PyList_SET_ITEM(%PYARG_0, i, %CONVERTTOPYTHON[QSGGeometry::Point2D](p));
+ PyList_SetItem(%PYARG_0, i, %CONVERTTOPYTHON[QSGGeometry::Point2D](p));
}
// @snippet qsgeometry-vertexdataaspoint2d
--- /dev/null
+// Copyright (C) 2024 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+// @snippet qtro-init
+PySide::RemoteObjects::init(module);
+// @snippet qtro-init
+
+// @snippet node-acquire
+auto *typeObject = reinterpret_cast<PyTypeObject*>(%PYARG_1);
+if (!PySide::inherits(typeObject, SbkPySide6_QtRemoteObjectsTypeStructs[SBK_QRemoteObjectReplica_IDX].fullName)) {
+ PyErr_SetString(PyExc_TypeError, "First argument must be a type deriving from QRemoteObjectReplica.");
+ return nullptr;
+}
+
+static PyObject *pyConstructWithNode = Shiboken::Enum::newItem(
+ Shiboken::Module::get(SbkPySide6_QtRemoteObjectsTypeStructs[SBK_QRemoteObjectReplica_ConstructorType_IDX]),
+ 1 /* protected QRemoteObjectReplica::ConstructorType::ConstructWithNode */
+);
+
+Shiboken::AutoDecRef args;
+if (pyArgs[1])
+ args.reset(PyTuple_Pack(3, %PYSELF, pyConstructWithNode, pyArgs[1]));
+else
+ args.reset(PyTuple_Pack(2, %PYSELF, pyConstructWithNode));
+
+PyObject *instance = PyObject_CallObject(%PYARG_1, args.object());
+if (!instance)
+ return nullptr; // Propagate the exception
+
+%PYARG_0 = instance;
+// @snippet node-acquire
PyErr_SetString(PyExc_ValueError, error.constData());
return -1;
}
+
+// PySide::Signal::getObject() increments the refcount for emitterPyObject,
+// but there is nothing that decrements the count when the spy goes out of
+// scope. It doesn't seem like QSignalSpy should prevent the target object
+// from being garbage collected. So we need to decrement the refcount here.
+Py_DECREF(emitterPyObject);
%0 = new QSignalSpy(emitter, signature.constData());
// @snippet qsignalspy-signal
%CPPSELF.%FUNCTION_NAME();
// @snippet qmessagebox-open-connect-accept
+// @snippet replace-widget-child
+$CHILD_TYPE* oldChild = %CPPSELF.$FUNCTION_GET_OLD();
+if (oldChild != nullptr && oldChild != $CPPARG) {
+ Shiboken::AutoDecRef pyChild(%CONVERTTOPYTHON[$CHILD_TYPE*](oldChild));
+ Shiboken::Object::setParent(nullptr, pyChild);
+ Shiboken::Object::releaseOwnership(pyChild);
+}
+Shiboken::Object::setParent(%PYSELF, $PYARG);
+// @snippet replace-widget-child
+
/*********************************************************************
* CONVERSIONS
********************************************************************/
<template name="pybytes_uint">
uint %out = static_cast<uint>(PyBytes_Size(%PYARG_1));
</template>
+
+ <template name="pysequencesize_int">
+ Py_ssize_t %out = PySequence_Size(%PYARG_1);
+ </template>
+
+ <!-- Convert an indexable C-style arrray %TYPE[%COUNT] to PySequence -->
+ <template name="c-array-to-pysequence">
+ auto *object = PyList_New(0);
+ for (int i = 0; i < %COUNT; i++) {
+ PyList_Append(object, %CONVERTTOPYTHON[%TYPE](%in[i]));
+ }
+ PyObject *%out = object;
+ </template>
+
+ <!-- Convert a PySequence to an indexable C-style arrray %TYPE[] via ArrayPointer -->
+ <template name="pysequence-to-c-array">
+ const Py_ssize_t count = PySequence_Size(%PYARG_1);
+ Shiboken::ArrayPointer<%TYPE> %out(count);
+ for (Py_ssize_t i = 0; i < count; ++i) {
+ Shiboken::AutoDecRef a(PySequence_GetItem(%PYARG_1, i));
+ %out[i] = %CONVERTTOCPP[%TYPE](a);
+ }
+ </template>
+
</typesystem>
<insert-template name="tuple_retval_ok"/>
</template>
- <template name="fix_bool*,arg,arg">
- bool ok_;
- %RETURN_TYPE retval_ = %CPPSELF.%FUNCTION_NAME(&ok_, %2, %3);
- <insert-template name="tuple_retval_ok"/>
- </template>
-
- <template name="fix_bool*,arg,arg,arg">
- bool ok_;
- %RETURN_TYPE retval_ = %CPPSELF.%FUNCTION_NAME(&ok_, %2, %3, %4);
- <insert-template name="tuple_retval_ok"/>
- </template>
-
- <template name="fix_bool*,arg,arg,arg,arg">
- bool ok_;
- %RETURN_TYPE retval_ = %CPPSELF.%FUNCTION_NAME(&ok_, %2, %3, %4, %5);
- <insert-template name="tuple_retval_ok"/>
- </template>
-
<!-- QInputDialog: these should allow threads -->
<template name="fix_arg,arg,arg,arg,arg,arg,arg,bool*,arg">
bool ok_;
<insert-template name="tuple_abcd_same_type"/>
</template>
- <template name="fix_number*,number*,number*,number*,args">
- $TYPE a, b, c, d;
- %CPPSELF->::%TYPE::%FUNCTION_NAME(&a, &b, &c, &d, %ARGUMENT_NAMES);
- <insert-template name="tuple_abcd_same_type"/>
- </template>
-
- <template name="fix_native_return_number*,number*,number*,number*">
- PyObject* _obj = %PYARG_0.object();
- Shiboken::AutoDecRef _obj0(PySequence_GetItem(_obj, 0));
- Shiboken::AutoDecRef _obj1(PySequence_GetItem(_obj, 1));
- Shiboken::AutoDecRef _obj2(PySequence_GetItem(_obj, 2));
- Shiboken::AutoDecRef _obj3(PySequence_GetItem(_obj, 3));
- if (!PySequence_Check(_obj)
- || PySequence_Size(_obj) != 4
- || !PyNumber_Check(_obj0)
- || !PyNumber_Check(_obj1)
- || !PyNumber_Check(_obj2)
- || !PyNumber_Check(_obj3)) {
- PyErr_SetString(PyExc_TypeError, "Sequence of 4 numbers expected");
- } else {
- *%1 = %CONVERTTOCPP[$TYPE](_obj0);
- *%2 = %CONVERTTOCPP[$TYPE](_obj1);
- *%3 = %CONVERTTOCPP[$TYPE](_obj2);
- *%4 = %CONVERTTOCPP[$TYPE](_obj3);
- }
- </template>
-
<template name="fix_number*,number*,number*,number*,number*">
$TYPE a, b, c, d, e;
%CPPSELF.%FUNCTION_NAME(&a, &b, &c, &d, &e);
PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[$TYPE](b));
</template>
- <template name="fix_arg,int*,int*">
- %RETURN_TYPE _ret;
- int a, b;
- _ret = %CPPSELF.%FUNCTION_NAME(%1, &a, &b);
- %PYARG_0 = PyTuple_New(3);
- PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](_ret));
- PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[int](a));
- PyTuple_SetItem(%PYARG_0, 2, %CONVERTTOPYTHON[int](b));
- </template>
-
<template name="return_tuple_QValidator_QString_int">
%RETURN_TYPE retval_ = %RETURN_TYPE(%CPPSELF.%FUNCTION_NAME(%1, %2));
%PYARG_0 = PyTuple_New(3);
Py_INCREF(%PYARG_0);
</template>
- <!-- Helpers for modifying "bool nativeEventFilter(QByteArray, void*, long *result)"
- to return a tuple of bool,long -->
+ <!-- Helpers for modifying "bool nativeEventFilter(QByteArray, void*, qintptr *result)"
+ to return a tuple of bool,qintptr -->
<template name="return_native_eventfilter_conversion_variables">
qintptr resultVar{0};
qintptr *%out = &resultVar;
</template>
- <template name="return_native_eventfilter_conversion">
- %RETURN_TYPE %out = false;
- if (PySequence_Check(%PYARG_0) && (PySequence_Size(%PYARG_0) == 2)) {
- Shiboken::AutoDecRef pyItem(PySequence_GetItem(%PYARG_0, 0));
- %out = %CONVERTTOCPP[bool](pyItem);
- if (result) {
- Shiboken::AutoDecRef pyResultItem(PySequence_GetItem(pyResult, 1));
- *result = %CONVERTTOCPP[long](pyResultItem);
- }
- }
- </template>
-
- <template name="return_native_eventfilter">
- %PYARG_0 = PyTuple_New(2);
- PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](%0));
- PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[long](*result_out));
- </template>
+ <template name="return_native_eventfilter_conversion"
+ file="../glue/qtcore.cpp" snippet="return-native-eventfilter-conversion"/>
<!-- templates for __reduce__ -->
<template name="reduce_code">
PyObject *pyRow = PyList_New(columnCount);
for (Py_ssize_t c = 0; c < columnCount; ++c) {
const %INTYPE_0 &cppItem = row->at(c);
- PyList_SET_ITEM(pyRow, c, %CONVERTTOPYTHON[%INTYPE_0](cppItem));
+ PyList_SetItem(pyRow, c, %CONVERTTOPYTHON[%INTYPE_0](cppItem));
}
- PyList_SET_ITEM(%out, r, pyRow);
+ PyList_SetItem(%out, r, pyRow);
}
return %out;
</template>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-->
<typesystem>
- <template name="QFontCharFix">
- int size = Shiboken::String::len(%PYARG_1);
- if (size == 1) {
- const char *str = Shiboken::String::toCString(%PYARG_1);
- const QChar ch(static_cast<unsigned short>(str[0]));
- %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(ch);
- %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
- } else {
- PyErr_SetString(PyExc_TypeError, "String must have only one character");
- }
- </template>
-
- <template name="load_xpm">
- Shiboken::AutoDecRef strList(PySequence_Fast(%PYARG_1, "Invalid sequence."));
- Py_ssize_t lineCount = PySequence_Size(strList.object());
- for (Py_ssize_t line = 0; line < lineCount; ++line) {
- Shiboken::AutoDecRef _obj(PySequence_GetItem(strList.object(), line));
- bool isString = Shiboken::String::check(_obj);
- if (!isString) {
- PyErr_SetString(PyExc_TypeError, "The argument must be a sequence of strings.");
- break;
- }
- }
- // PySIDE-1735: Enums are now implemented in Python, so we need to avoid asserts.
- if (PyErr_Occurred())
- break;
-
- auto xpm = new const char*[lineCount];
- for (Py_ssize_t line = 0; line < lineCount; ++line) {
- Shiboken::AutoDecRef _obj(PySequence_GetItem(strList.object(), line));
- xpm[line] = Shiboken::String::toCString(_obj);
- }
- %0 = new %TYPE(xpm);
+ <template name="qimage_buffer_constructor"
+ file="../glue/qtgui.cpp" snippet="qimage-buffer-constructor"/>
- delete [] xpm;
- </template>
-
- <template name="qmatrix_map">
- %ARG1_TYPE a, b;
- %CPPSELF.%FUNCTION_NAME(%1, %2, &a, &b);
- %PYARG_0 = PyTuple_New(2);
- PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[%ARG1_TYPE](a));
- PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[%ARG1_TYPE](b));
- </template>
-
- <template name="qimage_buffer_constructor">
- Py_INCREF(%PYARG_1);
- auto ptr = reinterpret_cast<uchar*>(Shiboken::Buffer::getPointer(%PYARG_1));
- %0 = new %TYPE(ptr, %ARGS, imageDecrefDataHandler, %PYARG_1);
- </template>
-
- <template name="qcolor_repr">
- switch(%CPPSELF.spec()) {
- case QColor::Rgb:
- {
- float r, g, b, a;
- %CPPSELF.getRgbF(&r, &g, &b, &a);
- QString repr = QString::asprintf("PySide6.QtGui.QColor.fromRgbF(%.6f, %.6f, %.6f, %.6f)", r, g, b, a);
- %PYARG_0 = Shiboken::String::fromCString(qPrintable(repr));
- break;
- }
- case QColor::Hsv:
- {
- float h, s, v, a;
- %CPPSELF.getHsvF(&h, &s, &v, &a);
- QString repr = QString::asprintf("PySide6.QtGui.QColor.fromHsvF(%.6f, %.6f, %.6f, %.6f)", h, s, v, a);
- %PYARG_0 = Shiboken::String::fromCString(qPrintable(repr));
- break;
- }
- case QColor::Cmyk:
- {
- float c, m, y, k, a;
- %CPPSELF.getCmykF(&c, &m, &y, &k, &a);
- QString repr = QString::asprintf("PySide6.QtGui.QColor.fromCmykF(%.6f, %.6f, %.6f, %.6f, %.6f)", c, m, y, k, a);
- %PYARG_0 = Shiboken::String::fromCString(qPrintable(repr));
- break;
- }
- case QColor::Hsl:
- {
- float h, s, l, a;
- %CPPSELF.getHslF(&h, &s, &l, &a);
- QString repr = QString::asprintf("PySide6.QtGui.QColor.fromHslF(%.6f, %.6f, %.6f, %.6f)", h, s, l, a);
- %PYARG_0 = Shiboken::String::fromCString(qPrintable(repr));
- break;
- }
- default:
- {
- %PYARG_0 = Shiboken::String::fromCString("PySide6.QtGui.QColor()");
- }
- }
- </template>
-
- <template name="validator_conversionrule">
- QValidator::State %out;
-
- if (PySequence_Check(%PYARG_0)) {
- Shiboken::AutoDecRef seq(PySequence_Fast(%PYARG_0, 0));
- const Py_ssize_t size = PySequence_Size(seq.object());
-
- if (size > 1) {
- Shiboken::AutoDecRef _obj1(PySequence_GetItem(seq.object(), 1));
- if (%ISCONVERTIBLE[QString](_obj1))
- %1 = %CONVERTTOCPP[QString](_obj1);
- else
- qWarning("%TYPE::%FUNCTION_NAME: Second tuple element is not convertible to unicode.");
- }
-
- if (size > 2) {
- Shiboken::AutoDecRef _obj2(PySequence_GetItem(seq.object(), 2));
- if (%ISCONVERTIBLE[int](_obj2))
- %2 = %CONVERTTOCPP[int](_obj2);
- else
- qWarning("%TYPE::%FUNCTION_NAME: Second tuple element is not convertible to int.");
- }
- Shiboken::AutoDecRef _sobj(PySequence_GetItem(seq.object(), 0));
-
- %PYARG_0.reset(_sobj);
- Py_INCREF(%PYARG_0); // we need to incref, because "%PYARG_0 = ..." will decref the tuple and the tuple will be decrefed again at the end of this scope.
- }
-
- // check retrun value
- if (%ISCONVERTIBLE[QValidator::State](%PYARG_0)) {
- %out = %CONVERTTOCPP[QValidator::State](%PYARG_0);
- } else {
- PyErr_Format(PyExc_TypeError, "Invalid return value in function %s, expected %s, got %s.",
- "QValidator.validate",
- "PySide6.QtGui.QValidator.State, (PySide6.QtGui.QValidator.State,), (PySide6.QtGui.QValidator.State, unicode) or (PySide6.QtGui.QValidator.State, unicode, int)",
- Py_TYPE(pyResult)->tp_name);
- return QValidator::State();
- }
- </template>
+ <template name="validator_conversionrule"
+ file="../glue/qtgui.cpp" snippet="validator-conversionrule"/>
<template name="qpainter_drawlist">
%CPPSELF.%FUNCTION_NAME(%1.constData(), %1.size());
return %CONVERTTOPYTHON[%RETURN_TYPE](*%CPPSELF);
</template>
- <template name="inplace_sub">
- *%CPPSELF -= %1;
- return %CONVERTTOPYTHON[%RETURN_TYPE](*%CPPSELF);
- </template>
-
- <template name="inplace_mult">
- *%CPPSELF *= %1;
- return %CONVERTTOPYTHON[%RETURN_TYPE](*%CPPSELF);
- </template>
-
- <template name="inplace_div">
- *%CPPSELF /= %1;
- return %CONVERTTOPYTHON[%RETURN_TYPE](*%CPPSELF);
- </template>
-
<template name="return_QString_native">
if (%ISCONVERTIBLE[QString](%PYARG_0))
%1 = %CONVERTTOCPP[QString](%PYARG_0);
qWarning("%TYPE::%FUNCTION_NAME: Argument is not convertible to unicode.");
</template>
- <template name="repr_code_matrix">
- QByteArray format(Py_TYPE(%PYSELF)->tp_name);
- format += QByteArrayLiteral("((");
+ <template name="repr_code_matrix"
+ file="../glue/qtgui.cpp" snippet="qmatrix-repr-code"/>
- QList< %MATRIX_TYPE > cppArgs;
- %MATRIX_TYPE data[%MATRIX_SIZE];
- %CPPSELF.copyDataTo(data);
- int matrixSize = %MATRIX_SIZE;
- for(int size=0; size < matrixSize; size++) {
- if (size > 0)
- format += ", ";
- format += QByteArray::number(data[size]);
- }
- format += "))";
+ <template name="reduce_code_matrix"
+ file="../glue/qtgui.cpp" snippet="qmatrix-reduce-code"/>
- %PYARG_0 = Shiboken::String::fromStringAndSize(format, format.size());
- </template>
-
- <template name="reduce_code_matrix">
- QList< %MATRIX_TYPE > cppArgs;
- %MATRIX_TYPE data[%MATRIX_SIZE];
- %CPPSELF.copyDataTo(data);
- int matrixSize = %MATRIX_SIZE;
- for(int size=0; size < matrixSize; size++)
- cppArgs.append(data[size]);
+ <template name="matrix_data_function"
+ file="../glue/qtgui.cpp" snippet="qmatrix-data-function"/>
- PyObject *type = PyObject_Type(%PYSELF);
- PyObject *args = Py_BuildValue("(N)",
- %CONVERTTOPYTHON[QList<%MATRIX_TYPE> ](cppArgs));
- %PYARG_0 = Py_BuildValue("(NN)", type, args);
- </template>
-
- <template name="matrix_data_function">
- const float* data = %CPPSELF.constData();
- PyObject *pyData = PyTuple_New(%MATRIX_SIZE);
- if (data) {
- for(int i=0; i < %MATRIX_SIZE; i++)
- PyTuple_SetItem(pyData, i, %CONVERTTOPYTHON[float](data[i]));
- }
- return pyData;
- </template>
-
- <template name="matrix_constructor">
- // PYSIDE-795: All PySequences can be made iterable with PySequence_Fast.
- Shiboken::AutoDecRef seq(PySequence_Fast(%PYARG_1, "Can't turn into sequence"));
- if (PySequence_Size(seq) == %SIZE) {
- Shiboken::AutoDecRef fast(PySequence_Fast(seq,
- "Failed to parse sequence on %TYPE constructor."));
- float values[%SIZE];
- for(int i=0; i < %SIZE; i++) {
- Shiboken::AutoDecRef pv(PySequence_GetItem(fast.object(), i));
- values[i] = %CONVERTTOCPP[float](pv);
- }
- %0 = new %TYPE(values);
- }
- </template>
+ <template name="matrix_constructor"
+ file="../glue/qtgui.cpp" snippet="qmatrix-constructor"/>
<template name="fix_args,QRectF*">
QRectF rect_;
uint %out = static_cast<uint>(PyBytes_Size(%PYARG_1));
</template>
+ <template name="fix_native_return_number*,number*,number*,number*"
+ file="../glue/qtgui.cpp" snippet="fix_margins_return"/>
</typesystem>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-// Copyright (C) 2018 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
--->
-<typesystem>
- <template name="callArrayFunction">
- Py_ssize_t _size = PySequence_Size(%PYARG_2);
- if (_size) {
- $ATTR_TYPE *_list = new $ATTR_TYPE[_size];
- if (_size) {
- Shiboken::AutoDecRef fast(PySequence_Fast(%PYARG_2,
- "Failed to parse sequence with type %VECTOR_TYPE."));
- for(Py_ssize_t i=0; i < _size; i++) {
- Shiboken::AutoDecRef pv(PySequence_GetItem(fast.object(), i));
- _list[i] = %CONVERTTOCPP[$ATTR_TYPE](pv);
- }
- }
- %CPPSELF.%FUNCTION_NAME(%1, _list, $ARG0);
- delete[] _list;
- } else {
- %CPPSELF.%FUNCTION_NAME(%1, reinterpret_cast<$ATTR_TYPE*>(nullptr), $ARG1);
- }
- </template>
- <template name="glGetString_return_QString">
- const GLubyte *us = %CPPSELF.%FUNCTION_NAME(%ARGUMENT_NAMES);
- const QString s = QString::fromLocal8Bit(reinterpret_cast<const char *>(us));
- %PYARG_0 = %CONVERTTOPYTHON[QString](s);
- </template>
-</typesystem>
-->
<typesystem>
- <template name="replace_child">
- $CHILD_TYPE* oldChild = %CPPSELF.$FUNCTION_GET_OLD();
- if (oldChild && (oldChild != $CPPARG)) {
- Shiboken::AutoDecRef pyChild(%CONVERTTOPYTHON[$CHILD_TYPE*](oldChild));
- Shiboken::Object::setParent(nullptr, pyChild);
- Shiboken::Object::releaseOwnership(pyChild);
- }
- Shiboken::Object::setParent(%PYSELF, $PYARG);
- </template>
-
- <template name="qgraphicsitem_pysequence">
- const Py_ssize_t numItems = PySequence_Size(%PYARG_1);
- Shiboken::AutoArrayPointer<QGraphicsItem*> %out(numItems);
- for (Py_ssize_t i = 0; i < numItems; ++i) {
- Shiboken::AutoDecRef _arg1(PySequence_GetItem(%PYARG_1, i));
- %out[i] = %CONVERTTOCPP[QGraphicsItem*](_arg1);
- }
- </template>
-
- <template name="qgraphicsitem_pyobject">
- Shiboken::AutoDecRef object(PyList_New(0));
- for (int i=0, max=numItems; i < max; i++) {
- PyList_Append(object, %CONVERTTOPYTHON[QGraphicsItem*](%in[i]));
- }
- PyObject *%out = object.object();
- </template>
+ <template name="replace_child"
+ file="../glue/qtwidgets.cpp" snippet="replace-widget-child"/>
+ <!-- Do pointer conversion here since QStyleOptionGraphicsItem is an "object-type". -->
<template name="qstyleoptiongraphicsitem_pyobject">
Shiboken::AutoDecRef option_object(PyList_New(0));
for (int i=0, max=numItems; i < max; i++) {
<template name="pysequence_qstyleoptiongraphicsitem">
const Py_ssize_t numOptions = PySequence_Size(%PYARG_2);
- Shiboken::AutoArrayPointer<QStyleOptionGraphicsItem> %out(numOptions);
+ Shiboken::ArrayPointer<QStyleOptionGraphicsItem> %out(numOptions);
for (Py_ssize_t i=0; i < numOptions; ++i) {
Shiboken::AutoDecRef _arg1(PySequence_GetItem(%PYARG_1, i));
%out[i] = %CONVERTTOCPP[QStyleOptionGraphicsItem](_arg1);
}
</template>
- <template name="pysequencesize_int">
- Py_ssize_t %out = PySequence_Size(%PYARG_1);
- </template>
-
</typesystem>
set(ld_prefix_list "")
list(APPEND ld_prefix_list "${pysidebindings_BINARY_DIR}/libpyside")
list(APPEND ld_prefix_list "${pysidebindings_BINARY_DIR}/libpysideqml")
+ list(APPEND ld_prefix_list "${pysidebindings_BINARY_DIR}/libpysideremoteobjects")
list(APPEND ld_prefix_list "${SHIBOKEN_SHARED_LIBRARY_DIR}")
if(WIN32)
list(APPEND ld_prefix_list "${QT6_INSTALL_PREFIX}/${QT6_INSTALL_BINS}")
endmacro()
macro(remove_skipped_modules)
- # Removing from the MODULES list the items that were defined with
- # -DSKIP_MODULES on command line
- if(SKIP_MODULES)
- foreach(s ${SKIP_MODULES})
- list(REMOVE_ITEM MODULES ${s})
- endforeach()
- endif()
foreach(m ${MODULES})
collect_module_if_found(${m})
set(quiet_argument "QUIET")
endif()
- find_package(${_qt_module_name} ${quiet_argument})
# If package is found, _name_found will be equal to 1
set(_name_found "${_qt_module_name}_FOUND")
# _name_dir will keep the path to the directory where the CMake rules were found
# Modules to be built unless specified by -DMODULES on command line
if(NOT MODULES)
set(MODULES "${ALL_ESSENTIAL_MODULES};${ALL_OPTIONAL_MODULES}")
+ set(required_modules ${ALL_ESSENTIAL_MODULES})
+ set(optional_modules ${ALL_OPTIONAL_MODULES})
+else()
+ set(required_modules ${MODULES})
+ set(optional_modules)
endif()
+list(REMOVE_ITEM MODULES ${SKIP_MODULES})
+list(REMOVE_ITEM required_modules ${SKIP_MODULES})
+list(REMOVE_ITEM optional_modules ${SKIP_MODULES})
+
+find_package(Qt6
+ COMPONENTS ${required_modules}
+ OPTIONAL_COMPONENTS ${optional_modules}
+)
# This will contain the set of modules for which bindings are not built.
set(DISABLED_MODULES "${ALL_ESSENTIAL_MODULES};${ALL_OPTIONAL_MODULES}")
# Whether to add libpysideqml
find_package(Qt6 COMPONENTS Qml)
+# Whether to add libpysideremoteobjects
+find_package(Qt6 COMPONENTS RemoteObjects)
+
string(REGEX MATCHALL "[0-9]+" qt_version_helper "${Qt${QT_MAJOR_VERSION}Core_VERSION}")
list(GET qt_version_helper 0 QT_VERSION_MAJOR)
-DPython_EXECUTABLE=/path/to/interpreter
.. note:: You can add `-DFORCE_LIMITED_API=yes` in case you want to have a
- build which will be compatible with Python 3.8+.
+ build which will be compatible with Python 3.9+.
and then for building::
.. note:: Keep in mind you need to use the same version as your Qt installation
-.. note:: With `uv`_, use `uv pip install ...
+.. note:: With `uv`_, use ``uv pip install ...``
Building PySide
~~~~~~~~~~~~~~~
.. _OpenSSL: https://sourceforge.net/projects/openssl/
.. _`Qt for Windows`: https://doc.qt.io/qt-6/windows.html
-Building from source on Windows 10
-----------------------------------
+Building from source on Windows
+-------------------------------
+
+Creating a Dev Drive
+~~~~~~~~~~~~~~~~~~~~
+
+We recommend using a `Dev Drive`_ for development work on Windows. This is a
+special partition with a fast file system that is excluded from virus scanning.
Creating a virtual environment
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
python examples\widgets\widgets\tetrix\tetrix.py
.. _`uv`: https://docs.astral.sh/uv/
+.. _`Dev Drive`: https://learn.microsoft.com/en-us/windows/dev-drive/
The final executable produced has a ``.exe`` suffix on Windows, ``.bin`` on Linux and ``.app`` on
macOS.
-.. note:: The default version of Nuitka used with the tool is version ``2.5.1``. This can be
+.. note:: The default version of Nuitka used with the tool is version ``2.7.11``. This can be
updated to a newer version by updating your ``pysidedeploy.spec`` file.
.. _how_pysidedeploy:
is the parent directory of the main Python entry point file
* ``input_file``: Path to the main Python entry point file
* ``project_file``: If it exists, this points to the path to the `Qt Creator Python Project File
- .pyproject <https://doc.qt.io/qtforpython-6/faq/typesoffiles.html
+ <https://doc.qt.io/qtforpython-6/faq/typesoffiles.html
#qt-creator-python-project-file-pyproject>`_ file. Such a file makes sure that the deployment
process never considers unnecessary files when bundling the executable.
* ``exec_directory``: The directory where the final executable is generated.
``snippets-translate``).
- Note that our examples need to have unique names due to the doc build.
- Verify that all slots are decorated using ``@Slot``.
+- Enumerations should be fully qualified (PYSIDE-1735).
- Add a ``.pyproject`` file (verify later on that docs build).
- Add a ``doc`` directory and descriptive ``.rst`` file,
and a screenshot if suitable (use ``optipng`` to reduce file size).
Build on the command line
=========================
-- Consider using ``build_scripts/qp5_tool.py``.
+- Consider using ``build_scripts/qfp_tool.py``.
Build with address sanitizer (Linux)
====================================
-ASAN needs to be told to not exit on memory leaks and its library
-needs to be pre-loaded. Assuming the library is found
-at ``/usr/lib/gcc/x86_64-linux-gnu/11``:
+`Address sanitizer <https://clang.llvm.org/docs/AddressSanitizer.html>`_
+(ASAN) needs to be told to not exit on memory leaks and its library needs to be
+pre-loaded. Assuming the library is found at
+``/usr/lib/gcc/x86_64-linux-gnu/11``:
.. code-block:: bash
export LD_PRELOAD=/usr/lib/gcc/x86_64-linux-gnu/11/libasan.so
python setup.py build [...] --sanitize-address
+Lately, this feature has been added to MVSC, too.
+
De-Virtualize the Python Files
==============================
--- /dev/null
+Fixing Type Hints
+=================
+
+Overview
+--------
+
+If you notice any type hint issues in your project while using mypy with the PySide6 API,
+this document provides guidance on how to :ref:`identify<finding-type-hints-issues>`,
+correct, and maintain accurate type hints. Improving type hints enhances IDE support, enables
+better code completion, and improves static analysis with tools like `mypy`_.
+
+PySide6 uses `Shiboken`_ to generate Python bindings from C++ headers, but:
+
+- Some type hints are missing or too generic (e.g., ``Any``)
+- Some are incorrect due to overloads or inheritance issues
+- Some APIs are dynamic or wrapped in unusual ways
+
+Fixing these improves developer experience for all users of PySide6.
+
+.. note:: Please refer to our `contribution guideline`_.
+
+
+Tools and Setup
+---------------
+
+To find and fix the type hints ensure that your development environment has PySide6 installed.
+
+.. code-block:: bash
+
+ python -m venv venv
+ source venv/bin/activate
+ pip install PySide6 mypy
+
+or, the entire PySide6 project can be cloned:
+
+.. code-block:: bash
+
+ git clone https://code.qt.io/pyside/pyside-setup.git
+ cd pyside-setup
+
+
+Finding Type Hints Issues
+-------------------------
+
+You can locate the type hints issues using a static analysis tool, such as ``mypy``.
+
+.. code-block:: bash
+
+ mypy your_project/
+
+
+How to Fix
+----------
+
+PySide6 uses `Shiboken`_ to generate bindings, and type information can come from either static
+`typesystem`_ XML files or dynamic Python-based signature definitions.
+
+1. Fixing with Typesystem Files
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Each Qt module has an associated XML `typesystem`_ (e.g., typesystem_widgets.xml). You can specify
+or override type hints using the pyi-type attribute.
+
+Example from typesystem_gui_common.xml:
+
+.. code-block:: xml
+
+ <modify-function signature="inverted(bool*)const">
+ <modify-argument index="return" pyi-type="Tuple[PySide6.QtGui.QTransform, bool]">
+ <replace-type modified-type="PyTuple"/>
+ </modify-argument>
+ </modify-function>
+
+
+2. Fixing with Python Signature Support
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Python scripts located under `shiboken module`_.
+
+Key Files:
+
+- ``enum_sig.py``: Enumerates all signatures of a class
+- ``pyi_generator.py``: Generates ``.pyi`` files for arbitrary modules
+- ``layout.py``: Configures differently formatted versions of signatures
+- ``mapping.py``: Maps the C++ types to Python equivalents
+- ``parser.py``: Parses the signature text and creates properties for the signature objects
+
+The Python scripts here are responsible for parsing signature texts, creating signatures, and
+applying custom rules to handle special behaviors in the PySide6 API.
+
+
+Rebuild and Verify
+------------------
+
+After modifying `typesystem`_ files or Python signature logic, `rebuild`_ PySide6 to regenerate
+``.pyi`` files:
+
+.. code-block:: bash
+
+ python setup.py build --qtpaths=/path/to/qtpaths --parallel=8 --reuse-build
+
+
+This regenerates the bindings and updates the stub files used for static analysis.
+
+To verify the changes, you can either run ``mypy`` on your own code:
+
+.. code-block:: bash
+
+ mypy your_project/
+
+Or use the dedicated script provided by PySide6 to validate the generated stubs:
+
+.. code-block:: bash
+
+ python sources/pyside6/tests/pysidetest/mypy_correctness_test.py
+
+This tool runs ``mypy`` against the generated stub files to detect type annotation issues and
+inconsistencies. It is typically used to validate ``.pyi`` stubs in the build directory after
+generation.
+
+
+Special Cases and Workarounds
+-----------------------------
+
+While most type hints in PySide6 are generated automatically through `typesystem`_ XML or parsed
+function signatures, there are a number of **special cases** that require manual intervention.
+
+Missing Optional Return Values
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Some functions in the PySide6 stub files are missing ``None`` as a valid return type. This usually
+happens when the C++ method returns a pointer, which may be ``nullptr``, but the automatic
+generator assumes a non-optional return in Python. To fix these cases, PySide6 maintains a list of
+such functions in the `mapping.py`_ file.
+
+Look for the set named:
+
+.. code-block:: python
+
+ missing_optional_return = {}
+
+This is a list of functions where the return type should be wrapped in ``Optional[...]``. These
+entries override the default behavior and ensure that the generated ``.pyi`` stub files correctly
+reflect that the function may return ``None``.
+
+Char* Argument Mapping
+^^^^^^^^^^^^^^^^^^^^^^
+
+In C++, ``char*`` is commonly used to represent both binary data (as ``bytes``) and null-terminated
+strings (as ``str``). By default, PySide6 maps ``char*`` to ``bytes`` in type hints.
+
+However, some Qt functions are known to expect text input and should be treated as accepting ``str``
+instead. To handle these exceptions, PySide6 overrides the default mapping for specific functions.
+This override is defined in the `mapping.py`_ file.
+
+Look for the following logic:
+
+.. code-block:: python
+
+ # Special case - char* can either be 'bytes' or 'str'. The default is 'bytes'.
+ # Here we manually set it to map to 'str'.
+ type_map_tuple.update({("PySide6.QtCore.QObject.setProperty", "char*"): str})
+ type_map_tuple.update({("PySide6.QtCore.QObject.property", "char*"): str})
+ type_map_tuple.update({("PySide6.QtCore.QObject.inherits", "char*"): str})
+ ...
+
+Each entry is a ``(function_name, argument_type)`` tuple mapped to the correct Python type. This
+ensures that generated stubs reflect the expected usage of ``str`` rather than ``bytes``.
+
+
+.. _mypy: https://pypi.org/project/mypy/
+.. _Shiboken: https://doc.qt.io/qtforpython-6/shiboken6/index.html
+.. _typesystem: https://doc.qt.io/qtforpython-6/shiboken6/typesystem.html
+.. _contribution guideline: https://wiki.qt.io/Qt_Contribution_Guidelines
+.. _rebuild: https://doc.qt.io/qtforpython-6/building_from_source/index.html
+.. _mapping.py: https://code.qt.io/cgit/pyside/pyside-setup.git/tree/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature/mapping.py
+.. _shiboken module: https://code.qt.io/cgit/pyside/pyside-setup.git/tree/sources/shiboken6/shibokenmodule/files.dir/shibokensupport/signature
signature_doc.rst
mypy-correctness.rst
feature-motivation.rst
+ remoteobjects.md
+ fix_type_hints.rst
and it would be better to change the code and replace this function.
-bytesobject.h
-~~~~~~~~~~~~~
-
-The macros ``PyBytes_AS_STRING`` and ``PyBytes_GET_SIZE`` were redefined to call
-the according functions.
-
-
-floatobject.h
-~~~~~~~~~~~~~
-
-``PyFloat_AS_DOUBLE`` now calls ``PyFloat_AsDouble``.
-
-
-tupleobject.h
-~~~~~~~~~~~~~
-
-``PyTuple_GET_ITEM``, ``PyTuple_SET_ITEM`` and ``PyTuple_GET_SIZE`` were redefined as
-function calls.
-
-
-listobject.h
-~~~~~~~~~~~~
-
-``PyList_GET_ITEM``, ``PyList_SET_ITEM`` and ``PyList_GET_SIZE`` were redefined as
-function calls.
-
-
dictobject.h
~~~~~~~~~~~~
methodobject.h
~~~~~~~~~~~~~~
-``PyCFunction_GET_FUNCTION``, ``PyCFunction_GET_SELF`` and ``PyCFunction_GET_FLAGS``
-were redefined as function calls.
-
Direct access to the methoddef structure is not available, and we defined
``PepCFunction_GET_NAMESTR`` as accessor for name strings.
-
pythonrun.h
~~~~~~~~~~~
--- /dev/null
+# Qt Remote Objects Overview
+
+[Qt Remote Objects](https://doc.qt.io/qt-6/qtremoteobjects-index.html) (or QtRO)
+is described as an IPC module. That puts the focus on the internal details.
+It should be looked at more as a Connected Framework.
+
+QtRO lets you easily take an existing Qt application and interact with it from
+other devices. QtRO allows you to create a
+[_Replica_](https://doc.qt.io/qt-6/qtremoteobjects-replica.html) QObject, making
+the Replica a surrogate for the real QOject in your program (called the
+[_Source_](https://doc.qt.io/qt-6/qtremoteobjects-source.html)). You interact with
+the Replica the same way you would the Source (with one important difference) and QtRO
+ensures those interactions are forwarded to the source for handling. Changes to the
+Source are cascaded to any Replicas.
+
+The mechanism Qt Remote Objects provides for enabling these objects to connect to each
+other are a network of
+[_Nodes_](https://doc.qt.io/qt-6/qtremoteobjects-node.html). Nodes handle the details of
+connecting processes or devices. A Replica is created by calling
+[acquire()](https://doc.qt.io/qt-6/qremoteobjectnode.html#acquire) on a Node, and Sources
+are shared on the network using
+[enableRemoting()](https://doc.qt.io/qt-6/qremoteobjecthostbase.html#enableRemoting).
+
+## Replicas are _latent copies_
+
+Qt Remote Object interactions are inherently asynchronous. This _can_ lead to
+confusing results initially
+
+```python
+# Assume a replica initially has an int property `i` with a value of 2
+print(f"Value of i on replica = {replica.i}") # prints 2
+replica.iChanged.connect(lambda i: print(f"Value of i on replica changed to {i}"))
+replica.i = 3
+print(f"Value of i on replica = {replica.i}") # prints 2, not 3
+
+# When the eventloop runs, the change will be forwarded to the source instance,
+# the change will be made, and the new i value will be sent back to the replica.
+# The iChanged signal will be fired
+# after some delay.
+```
+
+Note: To avoid this confusion, Qt Remote Objects can change setters to "push"
+slots on the Replica class, making the asynchronous nature of the behavior
+clear.
+
+```python
+replica.pushI(3) # Request a change to `i` on the source object.
+```
+
+## How does this affect PySide?
+
+PySide wraps the Qt C++ classes used by QtRO, so much of the needed
+functionality for QtRO is available in PySide. However, the interaction between
+a Source and Replica are in effect a contract that is defined on a _per object_
+basis. I.e., different objects have different APIs, and every participant must
+know about the contracts for the objects they intend to use.
+
+In C++, Qt Remote Objects leverages the
+[Replica Compiler (repc)](https://doc.qt.io/qt-6/qtremoteobjects-repc.html) to
+generate QObject header and C++ code that enforce the contracts for each type.
+REPC uses a simplified text syntax to describe the desired API in .rep files.
+REPC is integrated with qmake and cmake, simplifying the process of leveraging
+QtRO in a C++ project. The challenges in PySide are
+1) To parse the .rep file to extract the desired syntax
+2) Allow generation of types that expose the desired API and match the needed
+ contract
+3) Provide appropriate errors and handling in cases that can't be dynamically
+ handled in Python.
+For example, C++ can register templated types such as a QMap<double, MyType>
+and serialize such types once registered. While Python can create a similar
+type, there isn't a path to dynamically serialize such a type so C++ could
+interpret it correctly on the other side of a QtRO network.
+
+Under the covers, QtRO leverages Qt's QVariant infrastructure heavily. For
+instance, a Replica internally holds a QVariantList where each element
+represents one of the exposed QProperty values. The property's QVariant is
+typed appropriately for the property, allows an autogenerated getter to (for
+instance with a float property) return `return variant.value<float >();`. This
+works well with PySide converters.
+
+## RepFile PySide type
+
+The first challenge is handled by adding a Python type RepFile can takes a .rep
+file and parses it into an Abstract Syntax Tree (AST) describing the type.
+
+A simple .rep might look like:
+```cpp
+class Thermistat
+{
+ PROP(int temp)
+}
+```
+
+The desired interface would be
+```python
+from pathlib import Path
+from PySide6.QtRemoteObjects import RepFile
+
+input_file = Path(__file__).parent / "thermistat.rep"
+rep_file = RepFile(input_file)
+```
+
+The RepFile holds dictionaries `source`, `replica` and `pod`. These use the
+names of the types as the key, and the value is the PyTypeObject* of the
+generated type meeting the desired contract:
+
+```python
+Source = rep_file.source["Thermistat"] # A Type object for Source implementation of the type
+Replica = rep_file.replica["Thermistat"] # A Type object for Replica implementation of the type
+```
+
+## Replica type
+
+A Replica for a given interface will be a distinct type. It should be usable
+directly from Python once instantiated and initialized.
+
+```python
+Replica = rep_file.replica["Thermistat"] # A Type object matching the Replica contract
+replica = node.acquire(Replica) # We need to tell the node what type to instantiate
+# These two lines can be combined
+replica_instance = node.acquire(rep_file.replica["Thermistat"])
+
+# If there is a Thermistat source on the network, our replica will get connected to it.
+if replica.isInitialized():
+ print(f"The current tempeerature is {replica.temp}")
+else:
+ replica.initialized.connect(lambda: print(f"replica is now initialized. Temp = {replica.temp}"))
+```
+
+## Source type
+
+Unlike a Replica, whose interface is a passthrough of another object, the
+Source needs to actually define the desired behavior. In C++, QtRO supports two
+modes for Source objects. A MyTypeSource C++ class is autogenerated that
+defines pure virtual getters and setters. This enables full customization of
+the implementation. A MyTypeSimpleSource C++ class is also autogenerated that
+creates basic data members for properties and getters/setters that work on
+those data members.
+
+The intent is to follow the SimpleSource pattern in Python if possible.
+
+```python
+ Thermistat = rep_file.source["Thermistat"]
+ class MyThermistat(Thermistat):
+ def __init__(self, parent = None):
+ super().__init__(parent)
+ # Get the current temp from the system
+ self.temp = get_temp_from_system()
+```
+
+## Realizing Source/Replica types in python
+
+Assume there is a RepFile for thermistat.rep that defines a Thermistat class
+interface.
+
+`ThermistatReplica = repFile.replica["Thermistat"]` should be a Shiboken.ObjectType
+type, with a base of QRemoteObjectReplica's shiboken type.
+
+`ThermistatSource = repFile.source["Thermistat"]` should be a abstract class of
+Shiboken.ObjectType type, with a base of QObject's shiboken type.
+
+Both should support new classes based on their type to customize behavior.
needed, because C++ based projects could be handle from ``.qmake`` or
``CMakeLists.txt`` file, which are not used with Python-based projects.
+The deprecated ``*.pyproject`` project files use JSON format.
+Here is an example of such file:
+
+.. code-block:: javascript
+
+ {
+ "files": ["library/server.py", "library/client.py", "logger.py"]
+ }
+
+*Qt Creator* 16.0 added support for the pyproject.toml file format, which
+is the modern standard for Python projects. PySide6 6.9.0 introduced support
+for this format, and it is recommended to use it. In order to migrate a
+.pyproject file, see `Migrating from *.pyproject to pyproject.toml`
+<https://doc.qt.io/qtforpython-6/tools/pyside6-project.html
+#migrating_from_pyproject_to_pyproject_toml>`_
+
Old versions of *Qt Creator*, provided a simple format with the ``.pyqtc``
extension, which were plain-text files with one-file-per-line::
library/client.py
logger.py
...
-
-There were limitations to this format, and further options that might be
-added that would not be supported, which was the motivation to create a
-``.pyproject`` file, which is a JSON-based file where more options could
-be added. Here is an example of such file:
-
-.. code-block:: javascript
-
- {
- "files": ["library/server.py", "library/client.py", "logger.py", ...]
- }
===============
`pyside6-project` is a command line tool for creating, building and deploying
-|project| applications. It operates on a project file which is also used by
-`Qt Creator`_.
+|project| applications. It operates on project files which are also supported
+by `Qt Creator`_.
-Project file format
--------------------
+A project file contains a list of the source files used in the project. Typically they are
+``.py``, ``.qml``, ``.qrc``, ``.ts``, or ``.ui`` files. It can also include other subproject files.
+Generated files such as compiled resources or compiled translations should not be included in the
+project file.
-The project file format is a simple `JSON`_-based format with the suffix
-``.pyproject`` listing all files of the project excluding generated files
-(typically ``.py``, ``.qml``, ``.qrc``, ``.ts``, or ``.ui`` files):
+Currently, two project file formats are supported. Since PySide6 version 6.9.0, the ``*.pyproject``
+file format is deprecated in favor of the new ``pyproject.toml`` file format. The ``*.pyproject``
+file format is still supported for backward compatibility, but it is recommended to migrate to the
+new ``pyproject.toml`` file format. See
+:ref:`Migrating from *.pyproject to pyproject.toml<migrating_from_pyproject_to_pyproject_toml>`
+for more information.
+
+``pyproject.toml``
+------------------
+
+PySide6 version 6.9.0 added support for the new Python standard ``pyproject.toml`` project file
+format. It is intended to replace the deprecated ``*.pyproject`` file format. The project source
+files are listed in the ``tool.pyside6-project`` table. For example:
+
+.. code-block:: toml
+
+ [project]
+ name = "myproject"
+
+ [tool.pyside6-project]
+ files = ["main.py", "main_window.py"]
+
+More information about the ``pyproject.toml`` file format can be found in
+`Python Packaging User Guide specification: "Writing your pyproject.toml"`_.
+
+``*.pyproject``
+---------------
+
+The deprecated ``*.pyproject`` project file uses a simple `JSON`_-based format. The source files
+are listed in the ``files`` array of the JSON root object. For example:
.. code-block:: json
{
- "files": ["main.py"]
+ "files": ["main.py", "main_window.py"]
}
-
Usage
-----
the below commands, passing the project name (directory):
*new-ui*
- Creates a new QtWidgets project with a *Qt Widgets Designer*-based main
- window.
+ Creates a new QtWidgets project with a *Qt Widgets Designer*-based main window.
*new-widget*
Creates a new QtWidgets project with a main window.
*new-quick*
Creates a new QtQuick project.
-The other commands take the project file as an argument.
+Using the optional ``--legacy-pyproject`` flag, the tool will create a legacy ``.pyproject`` file
+instead of a ``pyproject.toml`` file.
+
+The following commands can receive a project file as an optional argument.
It is also possible to specify a directory containing the project file.
*build*
- Builds the project, generating the required build artifacts
+ Builds the project. Compiles resources, UI files, and QML files if existing and necessary.
(see :ref:`tutorial_uifiles`, :ref:`tutorial_qrcfiles`).
*run*
Updates translation (.ts) files (see :ref:`tutorial_translations`).
*clean*
- Cleans the build artifacts.
+ Cleans the build artifacts. For example, compiled resources.
*qmllint*
Runs the ``qmllint`` tool, checking the QML files.
+*migrate-pyproject*
+ Migrates the content of one or more ``*.pyproject`` files to a ``pyproject.toml`` file.
+ See :ref:`Migrating from *.pyproject to pyproject.toml <migrating_from_pyproject_to_pyproject_toml>`.
+
+Considerations
+--------------
+
+For each file entry in the project files, ``pyside6-project`` does the following:
+
+ * ``<other project file>``: Recursively handle subproject
+ * ``<name>.qrc``: Runs the resource compiler to create a file rc_<name>.py
+ * ``<name>.ui``: Runs the user interface compiler to create a file ui_<name>.py
+
+For a Python file declaring a QML module, a directory matching the URI is
+created and populated with .qmltypes and qmldir files for use by code analysis
+tools. Currently, only one QML module consisting of several classes can be
+handled per project file.
+
+.. _migrating_from_pyproject_to_pyproject_toml:
+
+Migrating from ``*.pyproject`` to ``pyproject.toml``
+----------------------------------------------------
+
+Since PySide6 6.9.0, ``pyside6-project`` tool can create a new ``pyproject.toml`` file or update an
+existing one with the existing ``*.pyproject`` file content. To migrate an existing project, run
+``pyside6-project`` command with the ``migrate-pyproject`` argument. For example:
+
+.. code-block:: bash
+
+ pyside6-project migrate-pyproject
+
+If no file is specified, the tool will read the ``*.pyproject`` files in the current working
+directory. In the case of having multiple ``*.pyproject`` files in the same directory, its contents
+will be merged. A new ``pyproject.toml`` file will be created if not existing. If the file already
+exists, a confirmation message is displayed to the user asking for confirmation before
+updating the file with the new content. For example:
+
+.. code-block:: bash
+
+ mkdir myproject
+ cd myproject
+ echo {"files": ["main.py", "my_widget.py"]} > myproject.pyproject
+ pyside6-project migrate-pyproject
+
+Generated pyproject.toml file:
+
+.. code-block:: toml
+
+ [project]
+ name = "myproject"
+
+ [tool.pyside6-project]
+ files = ["main.py", "my_widget.py"]
.. _`Qt Creator`: https://www.qt.io/product/development-tools
+.. _`Python Packaging User Guide specification: "Writing your pyproject.toml"`: https://packaging.python.org/en/latest/guides/writing-pyproject-toml/
.. _`JSON`: https://www.json.org/
export QT_LOGGING_RULES="qt.pyside.libpyside.warning=true"
+Thread affinity
++++++++++++++++
+
+In a multi-threaded application, signals can be emitted from senders belonging
+to a different thread than the receiver. For non-Slot type receivers, the code
+is then executed in the sender's thread context.
+
+However, for methods of :class:`~PySide6.QtCore.QObject` derived classes
+decorated with @Slot, this is usually different since they are associated with
+threads (see :meth:`~PySide6.QtCore.QObject.moveToThread`). This depends on
+last parameter of the :meth:`~PySide6.QtCore.QObject.connect` method which is
+of type :class:`PySide6.QtCore.Qt.ConnectionType`.
+
+When ``Qt.ConnectionType.AutoConnection`` (default) or
+``Qt.ConnectionType.QueuedConnection`` are passed, the receiver code will be
+executed in the thread context of the receiver object. This is useful for
+passing results from background threads into GUI classes, which need to use the
+main thread.
+
.. _overloading-signals-and-slots:
Overloading Signals and Slots with Different Types
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
-from PySide6.QtCore import Slot
from PySide6.QtGui import QAction, QKeySequence
from PySide6.QtWidgets import QMainWindow
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
-from PySide6.QtCore import QDateTime, Qt
from PySide6.QtGui import QPainter
from PySide6.QtWidgets import (QWidget, QHeaderView, QHBoxLayout, QTableView,
QSizePolicy)
-from PySide6.QtCharts import QChart, QChartView, QLineSeries, QDateTimeAxis, QValueAxis
+from PySide6.QtCharts import QChart, QChartView
from table_model import CustomTableModel
self.main_layout = QHBoxLayout()
size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
- ## Left layout
+ # Left layout
size.setHorizontalStretch(1)
self.table_view.setSizePolicy(size)
self.main_layout.addWidget(self.table_view)
- ## Right Layout
+ # Right Layout
size.setHorizontalStretch(4)
self.chart_view.setSizePolicy(size)
self.main_layout.addWidget(self.chart_view)
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
-from PySide6.QtCore import Slot
-from PySide6.QtGui import QAction, QKeySequence, QScreen
+from PySide6.QtGui import QAction, QKeySequence
from PySide6.QtWidgets import QMainWindow
QVBoxLayout, QWidget)
from PySide6.QtCharts import QtCharts
-from __feature__ import snake_case, true_property
+from __feature__ import snake_case, true_property # noqa: F401
class Widget(QWidget):
super().__init__()
self.setWindowTitle("Tutorial")
+
if __name__ == "__main__":
# Qt Application
app = QApplication(sys.argv)
language: python
caption: financemodel.py
linenos: true
-emphasize-lines: 39-61, 104-114
+emphasize-lines: 45-55, 92-103
---
```
</details>
-Two methods are overridden for the `FinanceModel` class - `fetchMore` and `canFetchMore`. These
-methods are used to fetch more data from the REST API when the model is scrolled to the end. The data
-is fetched in chunks of 10 entries at a time. Additionally, the `append` method is updated to send a
-POST request to the REST API to add a new finance entry.
+A new method `fetchAllData` is added to the `FinanceModel` class to fetch all the finance data from
+the REST API. This method is called when the application starts to populate the model with the
+existing finance data. Additionally, the `append` method is updated to send a POST request to the
+REST API to add a new finance entry.
### Updating the Main Python File for the Frontend
"""A simple model that uses a QStringList as its data source."""
def __init__(self, strings, parent=None):
- super().__init__(strings, parent)
+ super().__init__(strings, parent)
#! [0]
return True
#! [0]
+
#! [1]
def dropMimeData(self, data, action, row, column, parent):
if not self.canDropMimeData(data, action, row, column, parent):
import sys
from PySide6.QtWidgets import (QApplication, QListView)
-from PySide6.QtCore import QAbstractListModel, QStringListModel, QModelIndex, Qt
+from PySide6.QtCore import QAbstractListModel, QModelIndex, Qt
#! [0]
model.insertRows(5, 7, QModelIndex())
for row in range(5, 12):
index = model.index(row, 0, QModelIndex())
- model.setData(index, f"{row+1}")
+ model.setData(index, f"{row + 1}")
#! [main5]
view.show()
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
-from PySide6.QtSql import QSqlDatabase, QSqlError, QSqlQuery
+from PySide6.QtSql import QSqlDatabase, QSqlQuery
from datetime import date
q.exec_()
return q.lastInsertId()
+
BOOKS_SQL = """
create table books(id integer primary key, title varchar, author integer,
genre integer, year integer, rating integer)
values(?, ?, ?, ?, ?)
"""
+
def init_db():
"""
init_db()
greeneId = add_author(q, "Graham Greene", date(1904, 10, 2))
pratchettId = add_author(q, "Terry Pratchett", date(1948, 4, 28))
- check(q.prepare,INSERT_GENRE_SQL)
+ check(q.prepare, INSERT_GENRE_SQL)
sfiction = add_genre(q, "Science Fiction")
fiction = add_genre(q, "Fiction")
fantasy = add_genre(q, "Fantasy")
- check(q.prepare,INSERT_BOOK_SQL)
+ check(q.prepare, INSERT_BOOK_SQL)
add_book(q, "Foundation", 1951, asimovId, sfiction, 3)
add_book(q, "Foundation and Empire", 1952, asimovId, sfiction, 4)
add_book(q, "Second Foundation", 1953, asimovId, sfiction, 3)
from __future__ import annotations
import copy
-import os
from pathlib import Path
from PySide6.QtSql import QSqlRelationalDelegate
-from PySide6.QtWidgets import (QItemDelegate, QSpinBox, QStyledItemDelegate,
- QStyle, QStyleOptionViewItem)
-from PySide6.QtGui import QMouseEvent, QPixmap, QPalette, QImage
-from PySide6.QtCore import QEvent, QSize, Qt, QUrl
+from PySide6.QtWidgets import QSpinBox, QStyle
+from PySide6.QtGui import QPixmap, QPalette
+from PySide6.QtCore import QEvent, QSize, Qt
class BookDelegate(QSqlRelationalDelegate):
if option.state & QStyle.State_Selected:
painter.fillRect(option.rect,
- option.palette.color(color_group, QPalette.Highlight))
+ option.palette.color(color_group, QPalette.Highlight))
rating = model.data(index, Qt.DisplayRole)
width = self.star.width()
height = self.star.height()
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
-from PySide6.QtSql import QSqlDatabase, QSqlError, QSqlQuery
+from PySide6.QtSql import QSqlDatabase, QSqlQuery
from datetime import date
q.exec_()
return q.lastInsertId()
+
BOOKS_SQL = """
create table books(id integer primary key, title varchar, author integer,
genre integer, year integer, rating integer)
values(?, ?, ?, ?, ?)
"""
+
def init_db():
"""
init_db()
greeneId = add_author(q, "Graham Greene", date(1904, 10, 2))
pratchettId = add_author(q, "Terry Pratchett", date(1948, 4, 28))
- check(q.prepare,INSERT_GENRE_SQL)
+ check(q.prepare, INSERT_GENRE_SQL)
sfiction = add_genre(q, "Science Fiction")
fiction = add_genre(q, "Fiction")
fantasy = add_genre(q, "Fantasy")
- check(q.prepare,INSERT_BOOK_SQL)
+ check(q.prepare, INSERT_BOOK_SQL)
add_book(q, "Foundation", 1951, asimovId, sfiction, 3)
add_book(q, "Foundation and Empire", 1952, asimovId, sfiction, 4)
add_book(q, "Second Foundation", 1953, asimovId, sfiction, 3)
import sys
-from PySide6.QtCore import Qt
from PySide6.QtSql import QSqlQueryModel
from PySide6.QtWidgets import QTableView, QApplication
from __future__ import annotations
import copy
-import os
from pathlib import Path
from PySide6.QtSql import QSqlRelationalDelegate
-from PySide6.QtWidgets import (QItemDelegate, QSpinBox, QStyledItemDelegate,
- QStyle, QStyleOptionViewItem)
-from PySide6.QtGui import QMouseEvent, QPixmap, QPalette, QImage
-from PySide6.QtCore import QEvent, QSize, Qt, QUrl
+from PySide6.QtWidgets import QSpinBox, QStyle
+from PySide6.QtGui import QPixmap, QPalette
+from PySide6.QtCore import QEvent, QSize, Qt
class BookDelegate(QSqlRelationalDelegate):
if option.state & QStyle.State_Selected:
painter.fillRect(option.rect,
- option.palette.color(color_group, QPalette.Highlight))
+ option.palette.color(color_group, QPalette.Highlight))
rating = model.data(index, Qt.DisplayRole)
width = self.star.width()
height = self.star.height()
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
-import copy, os
from PySide6.QtSql import QSqlRelationalDelegate
-from PySide6.QtWidgets import (QItemDelegate, QSpinBox, QStyledItemDelegate,
- QStyle, QStyleOptionViewItem)
-from PySide6.QtGui import QMouseEvent, QPixmap, QPalette, QImage
-from PySide6.QtCore import QEvent, QSize, Qt, QUrl
+from PySide6.QtWidgets import QSpinBox, QStyle
+from PySide6.QtGui import QPixmap, QPalette
+from PySide6.QtCore import QEvent, QSize, Qt
+
class BookDelegate(QSqlRelationalDelegate):
"""Books delegate to rate the books"""
def __init__(self, star_png, parent=None):
- QSqlRelationalDelegate.__init__(self, parent)
+ super().__init__(parent)
self.star = QPixmap(":/images/star.png")
def paint(self, painter, option, index):
"""
if index.column() != 5:
# Since we draw the grid ourselves:
- opt = copy.copy(option)
- opt.rect = option.rect.adjusted(0, 0, -1, -1)
- QSqlRelationalDelegate.paint(self, painter, opt, index)
+ QSqlRelationalDelegate.paint(self, painter, option, index)
else:
model = index.model()
if option.state & QStyle.State_Enabled:
if option.state & QStyle.State_Selected:
painter.fillRect(option.rect,
- option.palette.color(color_group, QPalette.Highlight))
+ option.palette.color(color_group, QPalette.Highlight))
rating = model.data(index, Qt.DisplayRole)
width = self.star.width()
height = self.star.height()
painter.drawPixmap(x, y, self.star)
x += width
- # Since we draw the grid ourselves:
- self.drawFocus(painter, option, option.rect.adjusted(0, 0, -1, -1))
-
pen = painter.pen()
painter.setPen(option.palette.color(QPalette.Mid))
painter.drawLine(option.rect.bottomLeft(), option.rect.bottomRight())
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
-from PySide6.QtGui import QAction
from PySide6.QtWidgets import (QAbstractItemView, QDataWidgetMapper,
- QHeaderView, QMainWindow, QMessageBox)
+ QHeaderView, QMainWindow, QMessageBox)
from PySide6.QtGui import QKeySequence
-from PySide6.QtSql import (QSqlRelation, QSqlRelationalTableModel, QSqlTableModel,
- QSqlError)
-from PySide6.QtCore import QAbstractItemModel, QObject, QSize, Qt, Slot
+from PySide6.QtSql import QSqlRelation, QSqlRelationalTableModel, QSqlTableModel
+from PySide6.QtCore import Qt
import createdb
from ui_bookwindow import Ui_BookWindow
from bookdelegate import BookDelegate
self.genreEdit.setModelColumn(model.relationModel(genre_idx).fieldIndex("name"))
# Lock and prohibit resizing of the width of the rating column:
- self.bookTable.horizontalHeader().setSectionResizeMode(model.fieldIndex("rating"),
- QHeaderView.ResizeToContents)
+ header = self.bookTable.horizontalHeader()
+ header.setSectionResizeMode(model.fieldIndex("rating"), QHeaderView.ResizeToContents)
mapper = QDataWidgetMapper(self)
mapper.setModel(model)
self.bookTable.setCurrentIndex(model.index(0, 0))
self.create_menubar()
- def showError(err):
+ def showError(self, err):
QMessageBox.critical(self, "Unable to initialize Database",
- "Error initializing database: " + err.text())
+ "Error initializing database: " + err.text())
def create_menubar(self):
file_menu = self.menuBar().addMenu(self.tr("&File"))
quit_action = file_menu.addAction(self.tr("&Quit"))
- quit_action.triggered.connect(qApp.quit)
+ quit_action.triggered.connect(qApp.quit) # noqa: F821
help_menu = self.menuBar().addMenu(self.tr("&Help"))
about_action = help_menu.addAction(self.tr("&About"))
about_action.setShortcut(QKeySequence.HelpContents)
about_action.triggered.connect(self.about)
aboutQt_action = help_menu.addAction("&About Qt")
- aboutQt_action.triggered.connect(qApp.aboutQt)
+ aboutQt_action.triggered.connect(qApp.aboutQt) # noqa: F821
def about(self):
- QMessageBox.about(self, self.tr("About Books"),
- self.tr("<p>The <b>Books</b> example shows how to use Qt SQL classes "
- "with a model/view framework."))
+ message = self.tr("<p>The <b>Books</b> example shows how to use Qt SQL classes "
+ "with a model/view framework.")
+ QMessageBox.about(self, self.tr("About Books"), message)
--- /dev/null
+{
+ "files": ["main.py", "bookdelegate.py", "books.qrc", "bookwindow.py", "bookwindow.ui", "createdb.py"]
+}
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
-from PySide6.QtSql import QSqlDatabase, QSqlError, QSqlQuery
+from PySide6.QtSql import QSqlDatabase, QSqlQuery
from datetime import date
q.exec_()
return q.lastInsertId()
+
BOOKS_SQL = """
create table books(id integer primary key, title varchar, author integer,
genre integer, year integer, rating integer)
values(?, ?, ?, ?, ?)
"""
+
def init_db():
"""
init_db()
greeneId = add_author(q, "Graham Greene", date(1904, 10, 2))
pratchettId = add_author(q, "Terry Pratchett", date(1948, 4, 28))
- check(q.prepare,INSERT_GENRE_SQL)
+ check(q.prepare, INSERT_GENRE_SQL)
sfiction = add_genre(q, "Science Fiction")
fiction = add_genre(q, "Fiction")
fantasy = add_genre(q, "Fantasy")
- check(q.prepare,INSERT_BOOK_SQL)
+ check(q.prepare, INSERT_BOOK_SQL)
add_book(q, "Foundation", 1951, asimovId, sfiction, 3)
add_book(q, "Foundation and Empire", 1952, asimovId, sfiction, 4)
add_book(q, "Second Foundation", 1953, asimovId, sfiction, 3)
import sys
from PySide6.QtWidgets import QApplication
from bookwindow import BookWindow
-import rc_books
+import rc_books # noqa: F401
if __name__ == "__main__":
app = QApplication([])
--- /dev/null
+# Resource object code (Python 3)
+# Created by: object code
+# Created by: The Resource Compiler for Qt version 6.9.0
+# WARNING! All changes made in this file will be lost!
+
+from PySide6 import QtCore
+
+qt_resource_data = b"\
+\x00\x00\x03\x0e\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xffa\
+\x00\x00\x00\x09pHYs\x00\x00\x0b\x11\x00\x00\x0b\x11\
+\x01\x7fd_\x91\x00\x00\x00\x07tIME\x07\xd4\x09\
+\x03\x12\x11\x08\x18~\xe5:\x00\x00\x00\x06bKGD\
+\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x02\x9bID\
+AT8\xcbc\x98:c\x1e#:\xe6\xe5d\xcf\x17\
+\x12\x12\x16\xc4&\x87\x8e\x19\xb0\x09v\xc6\x18\xb7x\xea\
+\x8b\xcd\x9c=o\x09i\x06,X4\x8f\xf1\xd2\xa5\x99\
+L\xb9\xa1\x16\xc5\xc7\xbb\xed\xff\x0a\xf2\xb2;M\x9f\xb5\
+\x908\x03\x16,\x9a\xcb\xf8\xe0\xde\x04\x96\xc7\x0f\xdby\
+\xe7MO\xc8\xfbv\xbf\xe5\xff\xb4\x0a\x9b\x9by\x851\
+\xdc\xd3g-\x82k\x983\x7f)\xe3l F1`\
+\xca\xf4y\x8c\xd7\xaeMg\x02i~\xf2\xa8Y\xe1\xd2\
+\xa5\xfa\xdc_\x9f7\xfd\xffx\xbf\xea\x7fE\x96m\x97\
+\x81\x81>'33\x8b\xa5\x9e8gi\xb8\x9e\xc0f\
+&&\xa6D\x14\x03&N\x9d\xc7x\xef\xdeD\x96'\
+\x0f[E\x9f>j\xd6\xbdu\xb3\x22\xef\xd7\xb7=\xff\
+\xbe\x7f\xe8\xfb\x7f~S\xcc\xef\x05\xc5\xea\x9fNOQ\
+\xfb\x7f\xbaM\xed\xbf\x87\x1a\xefn5-\x1dV\x14\x03\
+f\xcf[\xce\xa8\xa4\xa9![W\xed\x9b}\xefJ\xcb\
+\xcew\xaf&\x7f\xfa\xfee\xc9\xff\xef\x1f\xfa\xff\xbf\xbf\
+\x95\xf2\xff\xc9^\x83\xffW\x17\xaa\xfdot\x12{\xc4\
+\xc7\xc7/\x8e\x12\x06Y\xb9\x85\xcc\xb2\x82\x1c\xf3\xa7D\
+\xab\xfe\xfa\xbe%\xe2\xff\x8fgm\xff\x7f|\x9a\x08\xd6\
+\xfc\xf5Y\xcd\xff\xd7\xe7\xfc\xfe\xdf\xde\xa0\xf5\x7fE\x94\
+\xecO\x16\x16V\xebi3\xe7\xa3\x06\xe2\xe4is\x18\
+\xe7/Z\xc1\xc8\xce\xc1i\x10\xe5\xa8\xd2\xbe\xa6\xcd\xe7\
+\xf6\xc3m\x99\xff~^(\xf8\xff\xe1j\xe0\xff\x17G\
+L\xff\xdf\xdf\xae\xf6\xbf\xc2]\xf4\xba\x9a\x9a\x06\x1bF\
+,\x00\x01#2\xe6\xe6\xe6`Q\xd6\x941_\xde\xe4\
+q\xfb\xc3y\xd3\xff\x1b\x8aT\xff\xbf?`\xff\xff\xdc\
+l\xe5\xff\xea\xc2\x1c9\xd3g-\xc0i\x00\x13\x10\x8b\
+\x03\xb1?\x10\xe7\xf5\x16\xd8\xde\xf8p\xc6\xe4\xbf\x9d<\
+\xf7t\x7fC\xe9\x95\xb7\x96\xd9\xff\x9b\x9c,\xfdN@\
+@H\x14\x9b\x01LP,\x06\xc4\x19@|\x22;\xca\
+\xf0\xe7\xe9\xf9\x06\xff\x81\xec\x03@\xbc^\x82\x9f\xf3\xf6\
+\x9e\x1a\xf3_az\x823P\xd2\x01T#3\x10\x0b\
+\x00\xb1\x1e\x10\x17\x03\xf1\xd1\xa8@\xdd\x9f\xad\x09J \
+\x03\xfe\x00\xf17 >\x0f\xb4kf\xb9\xa7\xea\x0d}\
+i>#d\x03\xb4\x808\x08\x88k\x81x\x09\xd4\xc6\
+\x1b\x11a\x06\xdf\xec\x94\xb8\xdf\x03\xd9;\x81x\x1a\x10\
+\xf7\x82\xd4\xb0\xb2\xb1G\xf9\xda\x99:L\x9d9\x9f\x09\
+f\x80\x0e\x10;\x02\xb1\x13\x10[\x00\xb1\x01\x10\x07\x06\
+{h\x9c\x02\xd2k\xa0\x86\x8b\x001\x17\x10\xf3\x80\xb0\
+\x88\x88(\xcb,hFC\xf6\x02\x08\xb3\x001+\x10\
+K122\xe4\x01i7 \x96\x01b6\xa88\x0b\
+T=cW\xef$\xb0\x01\x00\xceo{\xf5UL\xf0\
+\xac\x00\x00\x00\x00IEND\xaeB`\x82\
+"
+
+qt_resource_name = b"\
+\x00\x06\
+\x07\x03}\xc3\
+\x00i\
+\x00m\x00a\x00g\x00e\x00s\
+\x00\x08\
+\x0a\x85X\x07\
+\x00s\
+\x00t\x00a\x00r\x00.\x00p\x00n\x00g\
+"
+
+qt_resource_struct = b"\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x01}\xc3\x96\xdb\xbd\
+"
+
+def qInitResources():
+ QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
+
+def qCleanupResources():
+ QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
+
+qInitResources()
--- /dev/null
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'bookwindow.ui'
+##
+## Created by: Qt User Interface Compiler version 6.9.0
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
+ QFont, QFontDatabase, QGradient, QIcon,
+ QImage, QKeySequence, QLinearGradient, QPainter,
+ QPalette, QPixmap, QRadialGradient, QTransform)
+from PySide6.QtWidgets import (QAbstractItemView, QApplication, QComboBox, QFormLayout,
+ QGroupBox, QHeaderView, QLabel, QLineEdit,
+ QMainWindow, QSizePolicy, QSpinBox, QTableView,
+ QVBoxLayout, QWidget)
+
+class Ui_BookWindow(object):
+ def setupUi(self, BookWindow):
+ if not BookWindow.objectName():
+ BookWindow.setObjectName(u"BookWindow")
+ BookWindow.resize(601, 420)
+ self.centralWidget = QWidget(BookWindow)
+ self.centralWidget.setObjectName(u"centralWidget")
+ self.vboxLayout = QVBoxLayout(self.centralWidget)
+#ifndef Q_OS_MAC
+ self.vboxLayout.setSpacing(6)
+#endif
+#ifndef Q_OS_MAC
+ self.vboxLayout.setContentsMargins(9, 9, 9, 9)
+#endif
+ self.vboxLayout.setObjectName(u"vboxLayout")
+ self.groupBox = QGroupBox(self.centralWidget)
+ self.groupBox.setObjectName(u"groupBox")
+ self.vboxLayout1 = QVBoxLayout(self.groupBox)
+#ifndef Q_OS_MAC
+ self.vboxLayout1.setSpacing(6)
+#endif
+#ifndef Q_OS_MAC
+ self.vboxLayout1.setContentsMargins(9, 9, 9, 9)
+#endif
+ self.vboxLayout1.setObjectName(u"vboxLayout1")
+ self.bookTable = QTableView(self.groupBox)
+ self.bookTable.setObjectName(u"bookTable")
+ self.bookTable.setSelectionBehavior(QAbstractItemView.SelectRows)
+
+ self.vboxLayout1.addWidget(self.bookTable)
+
+ self.groupBox_2 = QGroupBox(self.groupBox)
+ self.groupBox_2.setObjectName(u"groupBox_2")
+ self.formLayout = QFormLayout(self.groupBox_2)
+ self.formLayout.setObjectName(u"formLayout")
+ self.label_5 = QLabel(self.groupBox_2)
+ self.label_5.setObjectName(u"label_5")
+
+ self.formLayout.setWidget(0, QFormLayout.ItemRole.LabelRole, self.label_5)
+
+ self.titleEdit = QLineEdit(self.groupBox_2)
+ self.titleEdit.setObjectName(u"titleEdit")
+ self.titleEdit.setEnabled(True)
+
+ self.formLayout.setWidget(0, QFormLayout.ItemRole.FieldRole, self.titleEdit)
+
+ self.label_2_2_2_2 = QLabel(self.groupBox_2)
+ self.label_2_2_2_2.setObjectName(u"label_2_2_2_2")
+
+ self.formLayout.setWidget(1, QFormLayout.ItemRole.LabelRole, self.label_2_2_2_2)
+
+ self.authorEdit = QComboBox(self.groupBox_2)
+ self.authorEdit.setObjectName(u"authorEdit")
+ self.authorEdit.setEnabled(True)
+
+ self.formLayout.setWidget(1, QFormLayout.ItemRole.FieldRole, self.authorEdit)
+
+ self.label_3 = QLabel(self.groupBox_2)
+ self.label_3.setObjectName(u"label_3")
+
+ self.formLayout.setWidget(2, QFormLayout.ItemRole.LabelRole, self.label_3)
+
+ self.genreEdit = QComboBox(self.groupBox_2)
+ self.genreEdit.setObjectName(u"genreEdit")
+ self.genreEdit.setEnabled(True)
+
+ self.formLayout.setWidget(2, QFormLayout.ItemRole.FieldRole, self.genreEdit)
+
+ self.label_4 = QLabel(self.groupBox_2)
+ self.label_4.setObjectName(u"label_4")
+
+ self.formLayout.setWidget(3, QFormLayout.ItemRole.LabelRole, self.label_4)
+
+ self.yearEdit = QSpinBox(self.groupBox_2)
+ self.yearEdit.setObjectName(u"yearEdit")
+ self.yearEdit.setEnabled(True)
+ self.yearEdit.setMaximum(2100)
+ self.yearEdit.setMinimum(-1000)
+
+ self.formLayout.setWidget(3, QFormLayout.ItemRole.FieldRole, self.yearEdit)
+
+ self.label = QLabel(self.groupBox_2)
+ self.label.setObjectName(u"label")
+
+ self.formLayout.setWidget(4, QFormLayout.ItemRole.LabelRole, self.label)
+
+ self.ratingEdit = QSpinBox(self.groupBox_2)
+ self.ratingEdit.setObjectName(u"ratingEdit")
+ self.ratingEdit.setMaximum(5)
+
+ self.formLayout.setWidget(4, QFormLayout.ItemRole.FieldRole, self.ratingEdit)
+
+
+ self.vboxLayout1.addWidget(self.groupBox_2)
+
+
+ self.vboxLayout.addWidget(self.groupBox)
+
+ BookWindow.setCentralWidget(self.centralWidget)
+ QWidget.setTabOrder(self.bookTable, self.titleEdit)
+ QWidget.setTabOrder(self.titleEdit, self.authorEdit)
+ QWidget.setTabOrder(self.authorEdit, self.genreEdit)
+ QWidget.setTabOrder(self.genreEdit, self.yearEdit)
+
+ self.retranslateUi(BookWindow)
+
+ QMetaObject.connectSlotsByName(BookWindow)
+ # setupUi
+
+ def retranslateUi(self, BookWindow):
+ BookWindow.setWindowTitle(QCoreApplication.translate("BookWindow", u"Books", None))
+ self.groupBox.setTitle(QCoreApplication.translate("BookWindow", u"Books", None))
+ self.groupBox_2.setTitle(QCoreApplication.translate("BookWindow", u"Details", None))
+ self.label_5.setText(QCoreApplication.translate("BookWindow", u"<b>Title:</b>", None))
+ self.label_2_2_2_2.setText(QCoreApplication.translate("BookWindow", u"<b>Author: </b>", None))
+ self.label_3.setText(QCoreApplication.translate("BookWindow", u"<b>Genre:</b>", None))
+ self.label_4.setText(QCoreApplication.translate("BookWindow", u"<b>Year:</b>", None))
+ self.yearEdit.setPrefix("")
+ self.label.setText(QCoreApplication.translate("BookWindow", u"<b>Rating:</b>", None))
+ # retranslateUi
import sys
import random
-from PySide6.QtWidgets import (QApplication, QLabel,
- QPushButton, QVBoxLayout, QWidget)
+from PySide6.QtWidgets import QApplication, QLabel, QPushButton, QVBoxLayout, QWidget
from PySide6.QtCore import Qt, Slot
+
class MyWidget(QWidget):
def __init__(self):
super().__init__()
def magic(self):
self.text.setText(random.choice(self.hello))
+
if __name__ == "__main__":
app = QApplication(sys.argv)
project(libpyside)
+find_package(Qt6 COMPONENTS Core CorePrivate)
+
set(libpyside_libraries Qt::Core Qt::CorePrivate)
set(CMAKE_AUTOMOC ON)
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "class_property.h"
-#include "pysidestaticstrings.h"
#include "feature_select.h"
+#include <basewrapper.h>
#include <pep384ext.h>
-#include <shiboken.h>
#include <sbkstaticstrings.h>
+#include <sbktypefactory.h>
+#include <signature.h>
extern "C" {
static const char *PyClassProperty_SignatureStrings[] = {
"PySide6.QtCore.PyClassProperty(cls,"
- "fget:typing.Optional[typing.Callable[[typing.Any],typing.Any]]=None,"
- "fset:typing.Optional[typing.Callable[[typing.Any,typing.Any],None]]=None,"
- "fdel:typing.Optional[typing.Callable[[typing.Any],None]]=None,"
+ "fget:typing.Optional[collections.abc.Callable[[typing.Any],typing.Any]]=None,"
+ "fset:typing.Optional[collections.abc.Callable[[typing.Any,typing.Any],None]]=None,"
+ "fdel:typing.Optional[collections.abc.Callable[[typing.Any],None]]=None,"
"doc:typing.Optional[str]=None)",
- "PySide6.QtCore.PyClassProperty.getter(cls,fget:typing.Callable[[typing.Any],typing.Any])->PySide6.QtCore.PyClassProperty",
- "PySide6.QtCore.PyClassProperty.setter(cls,fset:typing.Callable[[typing.Any,typing.Any],None])->PySide6.QtCore.PyClassProperty",
- "PySide6.QtCore.PyClassProperty.deleter(cls,fdel:typing.Callable[[typing.Any],None])->PySide6.QtCore.PyClassProperty",
+ "PySide6.QtCore.PyClassProperty.getter(cls,fget:collections.abc.Callable[[typing.Any],typing.Any])->PySide6.QtCore.PyClassProperty",
+ "PySide6.QtCore.PyClassProperty.setter(cls,fset:collections.abc.Callable[[typing.Any,typing.Any],None])->PySide6.QtCore.PyClassProperty",
+ "PySide6.QtCore.PyClassProperty.deleter(cls,fdel:collections.abc.Callable[[typing.Any],None])->PySide6.QtCore.PyClassProperty",
nullptr}; // Sentinel
void init(PyObject *module)
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "dynamicqmetaobject.h"
-#include "pysidelogging_p.h"
#include "pysideqobject.h"
#include "pysidesignal.h"
#include "pysidesignal_p.h"
#include "pysideproperty_p.h"
#include "pysideslot_p.h"
#include "pysideqenum.h"
-#include "pyside_p.h"
#include "pysidestaticstrings.h"
-#include <shiboken.h>
+#include <autodecref.h>
+#include <gilstate.h>
+#include <sbkstaticstrings.h>
+#include <sbkstring.h>
-#include <QtCore/QByteArray>
-#include <QtCore/QObject>
-#include <QtCore/QStringList>
-#include <QtCore/QTextStream>
-#include <QtCore/QList>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qobject.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qtextstream.h>
+#include <QtCore/qlist.h>
#include <private/qmetaobjectbuilder_p.h>
#include <cstring>
basesToCheck.push_back(type);
auto *sbkObjTypeF = SbkObject_TypeF();
- auto *baseObjType = reinterpret_cast<PyTypeObject *>(&PyBaseObject_Type);
+ auto *baseObjType = &PyBaseObject_Type;
for (Py_ssize_t i = 0; i < basesCount; ++i) {
auto *baseType = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(mro, i));
if (baseType != sbkObjTypeF && baseType != baseObjType
#include <sbkpython.h>
#include <pysidemacros.h>
-#include <QtCore/QMetaObject>
-#include <QtCore/QMetaMethod>
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qmetaobject.h>
#include <utility>
#include "signalmanager.h"
#include <autodecref.h>
+#include <helper.h>
#include <gilstate.h>
#include <pep384ext.h>
-#include <QtCore/QDebug>
-#include <QtCore/QtCompare>
-#include <QtCore/QCoreApplication>
-#include <QtCore/QHash>
-#include <QtCore/QPointer>
+#include <QtCore/qdebug.h>
+#include <QtCore/qcompare.h>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qpointer.h>
namespace PySide
{
{
if (PyMethod_Check(callback) != 0)
return SlotType::Method;
- if (PySide::isCompiledMethod(callback) != 0)
+ if (Shiboken::isCompiledMethod(callback))
return SlotType::CompiledMethod;
if (PyCFunction_Check(callback) != 0)
return SlotType::C_Function;
<< ", function=" << PySide::debugPyObject(m_function) << ')';
}
-// Store a weak reference on pythonSelf.
-class TrackingMethodDynamicSlot : public MethodDynamicSlot
-{
- Q_DISABLE_COPY_MOVE(TrackingMethodDynamicSlot)
-public:
- explicit TrackingMethodDynamicSlot(PyObject *function, PyObject *pythonSelf,
- PyObject *weakRef);
- ~TrackingMethodDynamicSlot() override;
-
- void releaseWeakRef() { m_weakRef = nullptr; }
-
-private:
- PyObject *m_weakRef;
-};
-
-TrackingMethodDynamicSlot::TrackingMethodDynamicSlot(PyObject *function, PyObject *pythonSelf,
- PyObject *weakRef) :
- MethodDynamicSlot(function, pythonSelf),
- m_weakRef(weakRef)
-{
-}
-
-TrackingMethodDynamicSlot::~TrackingMethodDynamicSlot()
-{
- if (m_weakRef != nullptr) {
- Shiboken::GilState gil;
- // weakrefs must not be de-refed after the object has been deleted,
- // else they get negative refcounts.
- if (PepExt_Weakref_IsAlive(m_weakRef))
- Py_DECREF(m_weakRef);
- }
-}
-
// Delete the connection on receiver deletion by weakref
-class PysideReceiverMethodSlot : public TrackingMethodDynamicSlot
+class PysideReceiverMethodSlot : public MethodDynamicSlot
{
Q_DISABLE_COPY_MOVE(PysideReceiverMethodSlot)
public:
static void onPysideReceiverSlotDestroyed(void *data)
{
- auto *self = reinterpret_cast<PysideReceiverMethodSlot *>(data);
- // Ensure the weakref is gone in case the connection stored in
- // Qt's internals outlives Python.
- self->releaseWeakRef();
+ auto *pythonSelf = reinterpret_cast<PyObject *>(data);
Py_BEGIN_ALLOW_THREADS
- disconnectReceiver(self->pythonSelf());
+ disconnectReceiver(pythonSelf);
Py_END_ALLOW_THREADS
}
PysideReceiverMethodSlot::PysideReceiverMethodSlot(PyObject *function, PyObject *pythonSelf) :
- TrackingMethodDynamicSlot(function, pythonSelf,
- WeakRef::create(pythonSelf, onPysideReceiverSlotDestroyed, this))
+ MethodDynamicSlot(function, pythonSelf)
{
+ // PYSIDE-3148: The weakref is automatically deleted when the notification triggers.
+ // Note that notifications may trigger after deletion of TrackingMethodDynamicSlot in case
+ // of multiple connections to the same receiver, so, &DynamicSlot must not be used as user
+ // data. Also trying to actively deref a pending weak ref from ~TrackingMethodDynamicSlot()
+ // does not reliably prevent the notification from being triggered.
+ WeakRef::create(pythonSelf, onPysideReceiverSlotDestroyed, pythonSelf);
}
DynamicSlot* DynamicSlot::create(PyObject *callback)
void SenderSignalDeletionTracker::senderDestroyed(QObject *o)
{
+ Shiboken::GilState gil; // PYSIDE-3072
for (auto it = connectionHash.begin(); it != connectionHash.end(); ) {
if (it.key().sender == o)
it = connectionHash.erase(it);
#include <sbkpython.h>
-#include <QtCore/QtCompare>
-#include <QtCore/QMetaObject>
+#include <QtCore/qcompare.h>
+#include <QtCore/qmetaobject.h>
QT_FORWARD_DECLARE_CLASS(QDebug)
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "feature_select.h"
-#include "pysidecleanup.h"
-#include "pysideqobject.h"
+#include "basewrapper.h"
#include "pysidestaticstrings.h"
#include "class_property.h"
-#include <shiboken.h>
+#include <autodecref.h>
#include <sbkfeature_base.h>
+#include <sbkstaticstrings.h>
+#include <sbkstring.h>
#include <signature_p.h>
-#include <QtCore/QStringList>
+#include <QtCore/qstringlist.h>
//////////////////////////////////////////////////////////////////////////////
//
#include <basewrapper.h>
#include <bindingmanager.h>
#include <gilstate.h>
+#include <helper.h>
#include <sbkconverter.h>
#include <sbkstring.h>
#include <sbkstaticstrings.h>
#include <sbkfeature_base.h>
#include <sbkmodule.h>
-#include <QtCore/QByteArray>
-#include <QtCore/QCoreApplication>
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QFileInfo>
-#include <QtCore/QMetaMethod>
-#include <QtCore/QMutex>
-#include <QtCore/QStack>
-#include <QtCore/QThread>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qmutex.h>
+#include <QtCore/qstack.h>
+#include <QtCore/qthread.h>
#include <QtCore/private/qobject_p.h>
#include <algorithm>
#ifdef Q_OS_WIN
# include <conio.h>
#else
-# include <QtCore/QDeadlineTimer>
+# include <QtCore/qdeadlinetimer.h>
# include <QtCore/private/qcore_unix_p.h>
#endif
}
if (!signalList.isEmpty()) {
auto *pySignal = reinterpret_cast<PyObject *>(
- Signal::newObjectFromMethod(self, signalList));
+ Signal::newObjectFromMethod(cppSelf, self, signalList));
PyObject_SetAttr(self, name, pySignal);
return pySignal;
}
if (existing != nullptr)
return reinterpret_cast<PyObject *>(existing)->ob_type;
// Find the best match (will return a PySide type)
- auto *sbkObjectType = Shiboken::ObjectType::typeForTypeName(typeName(cppSelf));
- if (sbkObjectType != nullptr)
- return reinterpret_cast<PyTypeObject *>(sbkObjectType);
- return nullptr;
+ return Shiboken::ObjectType::typeForTypeName(typeName(cppSelf));
}
PyObject *getWrapperForQObject(QObject *cppSelf, PyTypeObject *sbk_type)
bool isCompiledMethod(PyObject *callback)
{
- return PyObject_HasAttr(callback, PySide::PySideName::im_func())
- && PyObject_HasAttr(callback, PySide::PySideName::im_self())
- && PyObject_HasAttr(callback, PySide::PySideMagicName::code());
+ return Shiboken::isCompiledMethod(callback);
}
static const unsigned char qt_resource_name[] = {
// This will disable the internal qt.conf which points to the PySide6 subdirectory (due to the
// subdirectory not existing anymore).
#ifndef PYPY_VERSION
- QString executablePath = QString::fromWCharArray(Py_GetProgramFullPath());
+ QString executablePath = QString::fromWCharArray(PyUnicode_AsWideCharString(PySys_GetObject("executable"), nullptr));
#else
// PYSIDE-535: FIXME: Add this function when available.
QString executablePath = QLatin1StringView("missing Py_GetProgramFullPath");
#include <pysidemacros.h>
-#include <QtCore/QList>
-#include <QtCore/QPoint>
-#include <QtCore/QPointF>
+#include <QtCore/qlist.h>
+#include <QtCore/qpoint.h>
+#include <QtCore/qpoint.h>
namespace PySide::Numpy
{
#include <sbkpython.h>
#include <pep384ext.h>
-#include <QtCore/QByteArray>
+#include <QtCore/qbytearray.h>
#include <array>
#include <string>
#include "pysideclassinfo_p.h"
#include "dynamicqmetaobject.h"
-#include <shiboken.h>
#include <signature.h>
+#include <sbktypefactory.h>
+#include <sbkstring.h>
extern "C"
{
#include <sbkpython.h>
-#include <QtCore/QByteArray>
-#include <QtCore/QList>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qlist.h>
namespace PySide::ClassInfo {
#include "pysideclassdecorator_p.h"
#include "pysideclassinfo.h"
-#include <QtCore/QMetaObject>
+#include <QtCore/qmetaobject.h>
struct PySideClassInfo;
#ifndef PYSIDE_LOGGING_P_H
#define PYSIDE_LOGGING_P_H
-#include <QtCore/QLoggingCategory>
+#include <QtCore/qloggingcategory.h>
Q_DECLARE_LOGGING_CATEGORY(lcPySide)
#include "pysidemetafunction.h"
#include "pysidemetafunction_p.h"
-#include <shiboken.h>
+#include <signalmanager.h>
+
+#include <autodecref.h>
+#include <basewrapper.h>
+#include <sbkconverter.h>
+#include <sbktypefactory.h>
#include <signature.h>
-#include <QtCore/QMetaMethod>
+#include <QtCore/qmetaobject.h>
+
+using namespace Qt::StringLiterals;
extern "C"
{
QString tmp;
converter.toCpp(obj, &tmp);
methValues[i] = tmp;
+ } else if (metaType.id() == PyObjectWrapper::metaTypeId()) {
+ // Manual conversion, see PyObjectWrapper converter registration
+ methValues[i] = QVariant::fromValue(PyObjectWrapper(obj.object()));
+ methArgs[i] = methValues[i].data();
} else {
converter.toCpp(obj, methArgs[i]);
}
#include <sbkpython.h>
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
extern "C"
{
#include "pysideproperty_p.h"
#include "pysidesignal.h"
#include "pysidesignal_p.h"
+#include "signalmanager.h"
-#include <shiboken.h>
+#include <autodecref.h>
#include <pep384ext.h>
+#include <sbkconverter.h>
+#include <sbkstaticstrings.h>
+#include <sbkstring.h>
+#include <sbktypefactory.h>
#include <signature.h>
using namespace Shiboken;
+using namespace Qt::StringLiterals;
+
extern "C"
{
switch (call) {
case QMetaObject::ReadProperty: {
AutoDecRef value(getValue(source));
- auto *obValue = value.object();
- if (obValue) {
- Conversions::SpecificConverter converter(typeName);
- if (converter) {
- converter.toCpp(obValue, args[0]);
- } else {
- // PYSIDE-2160: Report an unknown type name to the caller `qtPropertyMetacall`.
- PyErr_SetObject(PyExc_StopIteration, obValue);
- }
+ if (value.isNull())
+ return;
+ if (typeName == "PyObject"_ba) {
+ // Manual conversion, see PyObjectWrapper converter registration
+ auto *pw = reinterpret_cast<PySide::PyObjectWrapper *>(args[0]);
+ pw->reset(value.object());
+ return;
+ }
+ if (Conversions::SpecificConverter converter(typeName); converter) {
+ converter.toCpp(value.object(), args[0]);
+ return;
}
+ // PYSIDE-2160: Report an unknown type name to the caller `qtPropertyMetacall`.
+ PyErr_SetObject(PyExc_StopIteration, value.object());
}
break;
static const char *Property_SignatureStrings[] = {
"PySide6.QtCore.Property(self,type:type,"
- "fget:typing.Optional[typing.Callable[[typing.Any],typing.Any]],"
- "fset:typing.Optional[typing.Callable[[typing.Any,typing.Any],None]],"
- "freset:typing.Optional[typing.Callable[[typing.Any,typing.Any],None]],"
+ "fget:typing.Optional[collections.abc.Callable[[typing.Any],typing.Any]]=None,"
+ "fset:typing.Optional[collections.abc.Callable[[typing.Any,typing.Any],None]]=None,"
+ "freset:typing.Optional[collections.abc.Callable[[typing.Any,typing.Any],None]]=None,"
"doc:str=None,"
- "notify:typing.Optional[typing.Callable[[],None]],"
+ "notify:typing.Optional[collections.abc.Callable[[],None]]=None,"
"designable:bool=True,scriptable:bool=True,"
"stored:bool=True,user:bool=False,constant:bool=False,final:bool=False)",
- "PySide6.QtCore.Property.deleter(self,fdel:typing.Callable[[typing.Any],None])->PySide6.QtCore.Property",
- "PySide6.QtCore.Property.getter(self,fget:typing.Callable[[typing.Any],typing.Any])->PySide6.QtCore.Property",
- "PySide6.QtCore.Property.read(self,fget:typing.Callable[[typing.Any],typing.Any])->PySide6.QtCore.Property",
- "PySide6.QtCore.Property.setter(self,fset:typing.Callable[[typing.Any,typing.Any],None])->PySide6.QtCore.Property",
- "PySide6.QtCore.Property.write(self,fset:typing.Callable[[typing.Any,typing.Any],None])->PySide6.QtCore.Property",
- "PySide6.QtCore.Property.__call__(self, func:typing.Callable[...,typing.Any])->PySide6.QtCore.Property",
+ "PySide6.QtCore.Property.deleter(self,fdel:collections.abc.Callable[[typing.Any],None])->PySide6.QtCore.Property",
+ "PySide6.QtCore.Property.getter(self,fget:collections.abc.Callable[[typing.Any],typing.Any])->PySide6.QtCore.Property",
+ "PySide6.QtCore.Property.read(self,fget:collections.abc.Callable[[typing.Any],typing.Any])->PySide6.QtCore.Property",
+ "PySide6.QtCore.Property.setter(self,fset:collections.abc.Callable[[typing.Any,typing.Any],None])->PySide6.QtCore.Property",
+ "PySide6.QtCore.Property.write(self,fset:collections.abc.Callable[[typing.Any,typing.Any],None])->PySide6.QtCore.Property",
+ "PySide6.QtCore.Property.__call__(self, func:collections.abc.Callable[...,typing.Any])->PySide6.QtCore.Property",
nullptr}; // Sentinel
void init(PyObject *module)
#include <sbkpython.h>
-#include <QtCore/QMetaObject>
+#include <QtCore/qmetaobject.h>
class PySidePropertyPrivate;
#include "pysideproperty.h"
#include <pysidemacros.h>
-#include <QtCore/QByteArray>
+#include <QtCore/qbytearray.h>
#include <QtCore/qtclasshelpermacros.h>
-#include <QtCore/QMetaObject>
+#include <QtCore/qmetaobject.h>
struct PySideProperty;
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-#include <shiboken.h>
-
#include "pysideqenum.h"
-#include "dynamicqmetaobject.h"
-#include "pyside_p.h"
+#include <autodecref.h>
+#include <sbkenum.h>
+#include <sbkstaticstrings.h>
+#include <sbkstring.h>
+
+#include <map>
///////////////////////////////////////////////////////////////
//
#ifndef PYSIDE_QENUM_H
#define PYSIDE_QENUM_H
+#include <sbkpython.h>
+
#include <pysidemacros.h>
+
#include <vector>
namespace PySide::QEnum {
#include <sbkpython.h>
-#include <QtCore/QHash>
+#include <QtCore/qhash.h>
namespace PySide
{
#ifndef PYSIDEQMETATYPE_H
#define PYSIDEQMETATYPE_H
-#include <QtCore/QMetaType>
+#include <QtCore/qmetatype.h>
namespace PySide
{
#include "pysidemacros.h"
#include <sbkpython.h>
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
#include <QtCore/qobjectdefs.h>
#include <memory>
#include "pysideqobject.h"
#include "pysideutils.h"
#include "pysidestaticstrings.h"
-#include "pysideweakref.h"
+#include "qobjectconnect.h"
#include "signalmanager.h"
-#include <shiboken.h>
-#include <sbkstaticstrings.h>
-
-#include <QtCore/QByteArray>
-#include <QtCore/QDebug>
-#include <QtCore/QHash>
-#include <QtCore/QObject>
-#include <QtCore/QMetaMethod>
-#include <QtCore/QMetaObject>
+#include <autodecref.h>
+#include <helper.h>
#include <pep384ext.h>
-#include <signature.h>
+#include <sbkconverter.h>
#include <sbkenum.h>
+#include <sbkstaticstrings.h>
+#include <sbkstring.h>
+#include <sbktypefactory.h>
+#include <signature.h>
+
+#include <QtCore/qbytearray.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qobject.h>
+#include <QtCore/qmetaobject.h>
#include <algorithm>
#include <optional>
static bool isSourceDeleted(const PySideSignalInstance *p)
{
- return p->d == nullptr || p->d->shared->deleted;
+ return p->d == nullptr || p->d->shared->source.isNull();
+}
+
+static inline QObject *sender(const PySideSignalInstance *p)
+{
+ return p->d != nullptr ? p->d->shared->source.data() : nullptr;
+}
+
+static inline QByteArray qSignalSignature(const PySideSignalInstance *p)
+{
+ return QT_SIGNAL_SENTINEL + p->d->signature;
}
static bool connection_Check(PyObject *o)
return result;
}
+static const char msgSourceDeleted[] = "Signal source has been deleted";
+static const char msgTargetSignalDeleted[] = "Target signal has been deleted";
+
+static SbkConverter *metaObjConnectionConverter()
+{
+ static SbkConverter *result = Shiboken::Conversions::getConverter("QMetaObject::Connection");
+ Q_ASSERT(result);
+ return result;
+}
+
namespace PySide::Signal {
static QByteArray buildSignature(const QByteArray &, const QByteArray &);
static void instanceInitialize(PySideSignalInstance *, PyObject *, PySideSignal *,
const PySideSignalInstanceSharedPtr &shared, int);
static PySideSignalData::Signature parseSignature(PyObject *);
- static PyObject *buildQtCompatible(const QByteArray &);
} // PySide::Signal
extern "C"
Py_DECREF(dataPvt->next);
dataPvt->next = nullptr;
}
- dataPvt->shared->deleted = true;
delete dataPvt;
self->d = nullptr;
}
ret.objCode = reinterpret_cast<PepCodeObject *>(PyFunction_GET_CODE(ret.function));
ret.functionName = PepFunction_GetName(ret.function);
- } else if (PySide::isCompiledMethod(slot)) {
+ } else if (Shiboken::isCompiledMethod(slot)) {
// PYSIDE-1523: PyFunction_Check and PyMethod_Check are not accepting compiled forms, we
// just go by attributes.
ret.isMethod = true;
"O|O:SignalInstance", const_cast<char **>(kwlist), &slot, &type))
return nullptr;
+ Qt::ConnectionType connectionType = Qt::AutoConnection;
+ if (type != nullptr && qstrcmp(Py_TYPE(type)->tp_name, "ConnectionType") == 0) {
+ static SbkConverter *connectionTypeConv =
+ Shiboken::Conversions::getConverter("Qt::ConnectionType");
+ Q_ASSERT(connectionTypeConv);
+ Shiboken::Conversions::pythonToCppCopy(connectionTypeConv, type, &connectionType);
+ }
+
auto *source = reinterpret_cast<PySideSignalInstance *>(self);
if (!source->d)
return PyErr_Format(PyExc_RuntimeError, "cannot connect uninitialized SignalInstance");
- if (isSourceDeleted(source))
- return PyErr_Format(PyExc_RuntimeError, "Signal source has been deleted");
-
- Shiboken::AutoDecRef pyArgs(PyList_New(0));
-
- bool match = false;
- if (Py_TYPE(slot) == PySideSignalInstance_TypeF()) {
- PySideSignalInstance *sourceWalk = source;
- //find best match
- while (sourceWalk && !match) {
- auto *targetWalk = reinterpret_cast<PySideSignalInstance *>(slot);
- while (targetWalk && !match) {
+ if (Py_TYPE(slot) == PySideSignalInstance_TypeF()) { // Connect signal to signal
+ // find best match
+ for (PySideSignalInstance *sourceWalk = source; sourceWalk != nullptr; ) {
+ for (auto *targetWalk = reinterpret_cast<PySideSignalInstance *>(slot);
+ targetWalk != nullptr; ) {
if (QMetaObject::checkConnectArgs(sourceWalk->d->signature,
targetWalk->d->signature)) {
- PyList_Append(pyArgs, sourceWalk->d->shared->source);
- Shiboken::AutoDecRef sourceSignature(PySide::Signal::buildQtCompatible(sourceWalk->d->signature));
- PyList_Append(pyArgs, sourceSignature);
-
- PyList_Append(pyArgs, targetWalk->d->shared->source);
- Shiboken::AutoDecRef targetSignature(PySide::Signal::buildQtCompatible(targetWalk->d->signature));
- PyList_Append(pyArgs, targetSignature);
-
- match = true;
+ if (isSourceDeleted(sourceWalk))
+ return PyErr_Format(PyExc_RuntimeError, msgSourceDeleted);
+ if (isSourceDeleted(targetWalk))
+ return PyErr_Format(PyExc_RuntimeError, msgTargetSignalDeleted);
+
+ auto conn = PySide::qobjectConnect(sender(sourceWalk),
+ qSignalSignature(sourceWalk).constData(),
+ sender(targetWalk),
+ qSignalSignature(targetWalk).constData(),
+ connectionType);
+ return Shiboken::Conversions::copyToPython(metaObjConnectionConverter(), &conn);
}
- targetWalk = reinterpret_cast<PySideSignalInstance *>(targetWalk->d->next);
+ targetWalk = targetWalk->d->next;
}
- sourceWalk = reinterpret_cast<PySideSignalInstance *>(sourceWalk->d->next);
+ sourceWalk = sourceWalk->d->next;
}
- } else {
- // Adding references to pyArgs
- PyList_Append(pyArgs, source->d->shared->source);
-
- // Check signature of the slot (method or function) to match signal
- PySideSignalInstance *matchedSlot = findSignalInstanceForSlot(source, slot);
- Shiboken::AutoDecRef signature(PySide::Signal::buildQtCompatible(matchedSlot->d->signature));
- PyList_Append(pyArgs, signature);
- PyList_Append(pyArgs, slot);
- match = true;
+ return PyErr_Format(PyExc_RuntimeError, "Failed to connect signal %s.",
+ source->d->signature.constData());
}
- if (type)
- PyList_Append(pyArgs, type);
-
- if (match) {
- Shiboken::AutoDecRef tupleArgs(PyList_AsTuple(pyArgs));
- Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source->d->shared->source,
- PySide::PySideName::qtConnect()));
- if (pyMethod.isNull()) // PYSIDE-79: check if pyMethod exists.
- return PyErr_Format(PyExc_RuntimeError, "method 'connect' vanished!");
- PyObject *result = PyObject_CallObject(pyMethod, tupleArgs);
- if (connection_Check(result))
- return result;
- Py_XDECREF(result);
+ if (PyCallable_Check(slot) == 0) {
+ return PyErr_Format(PyExc_TypeError, "Expected signal or callable, got \"%s\"",
+ Py_TYPE(slot)->tp_name);
}
- if (!PyErr_Occurred()) // PYSIDE-79: inverse the logic. A Null return needs an error.
- PyErr_Format(PyExc_RuntimeError, "Failed to connect signal %s.",
- source->d->signature.constData());
- return nullptr;
+ if (isSourceDeleted(source))
+ return PyErr_Format(PyExc_RuntimeError, msgSourceDeleted);
+ PySideSignalInstance *matchedSlot = findSignalInstanceForSlot(source, slot);
+ auto conn = PySide::qobjectConnectCallback(sender(source),
+ qSignalSignature(matchedSlot).constData(),
+ slot, connectionType);
+ return Shiboken::Conversions::copyToPython(metaObjConnectionConverter(), &conn);
}
static int argCountInSignature(const char *signature)
if (!source->d)
return PyErr_Format(PyExc_RuntimeError, "cannot emit uninitialized SignalInstance");
- // PYSIDE-2201: Check if the object has vanished meanwhile.
- // Tried to revive it without exception, but this gives problems.
if (isSourceDeleted(source))
- return PyErr_Format(PyExc_RuntimeError, "The SignalInstance object was already deleted");
+ return PyErr_Format(PyExc_RuntimeError, msgSourceDeleted);
Shiboken::AutoDecRef pyArgs(PyList_New(0));
Py_ssize_t numArgsGiven = PySequence_Size(args);
}
}
}
- Shiboken::AutoDecRef sourceSignature(PySide::Signal::buildQtCompatible(source->d->signature));
-
- PyList_Append(pyArgs, sourceSignature);
- for (Py_ssize_t i = 0, max = PyTuple_Size(args); i < max; i++)
- PyList_Append(pyArgs, PyTuple_GetItem(args, i));
- Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source->d->shared->source,
- PySide::PySideName::qtEmit()));
-
- Shiboken::AutoDecRef tupleArgs(PyList_AsTuple(pyArgs));
- return PyObject_CallObject(pyMethod.object(), tupleArgs);
+ const bool ok = PySide::SignalManager::emitSignal(sender(source),
+ qSignalSignature(source).constData(),
+ args);
+ if (PyErr_Occurred() != nullptr)
+ return nullptr;
+ if (ok)
+ Py_RETURN_TRUE;
+ Py_RETURN_FALSE;
}
static PyObject *signalInstanceGetItem(PyObject *self, PyObject *key)
if (PyTuple_Check(args) && PyTuple_Size(args))
slot = PyTuple_GetItem(args, 0);
- bool match = false;
- if (Py_TYPE(slot) == PySideSignalInstance_TypeF()) {
+ if (Py_TYPE(slot) == PySideSignalInstance_TypeF()) { // Disconnect signal from signal
auto *target = reinterpret_cast<PySideSignalInstance *>(slot);
- if (QMetaObject::checkConnectArgs(source->d->signature, target->d->signature)) {
- PyList_Append(pyArgs, source->d->shared->source);
- Shiboken::AutoDecRef source_signature(PySide::Signal::buildQtCompatible(source->d->signature));
- PyList_Append(pyArgs, source_signature);
-
- PyList_Append(pyArgs, target->d->shared->source);
- Shiboken::AutoDecRef target_signature(PySide::Signal::buildQtCompatible(target->d->signature));
- PyList_Append(pyArgs, target_signature);
- match = true;
+ if (!QMetaObject::checkConnectArgs(source->d->signature, target->d->signature)) {
+ warnDisconnectFailed(slot, source->d->signature);
+ Py_RETURN_FALSE;
}
- } else if (connection_Check(slot)) {
- PyList_Append(pyArgs, slot);
- match = true;
- } else {
- // try the matching signature, fall back to first
- auto *matchedSlot = slot != Py_None ? findSignalInstanceForSlot(source, slot) : source;
- PyList_Append(pyArgs, matchedSlot->d->shared->source);
- Shiboken::AutoDecRef signature(PySide::Signal::buildQtCompatible(matchedSlot->d->signature));
- PyList_Append(pyArgs, signature);
-
- // disconnect all, so we need to use the c++ signature disconnect(qobj, signal, 0, 0)
- if (slot == Py_None)
- PyList_Append(pyArgs, slot);
- PyList_Append(pyArgs, slot);
- match = true;
+
+ if (isSourceDeleted(source))
+ return PyErr_Format(PyExc_RuntimeError, msgSourceDeleted);
+ if (isSourceDeleted(target))
+ return PyErr_Format(PyExc_RuntimeError, msgTargetSignalDeleted);
+
+ auto *qSender = sender(source);
+ const bool ok = qSender->disconnect(qSignalSignature(source).constData(),
+ sender(target),
+ qSignalSignature(target).constData());
+ if (ok)
+ Py_RETURN_TRUE;
+ warnDisconnectFailed(slot, source->d->signature);
+ Py_RETURN_FALSE;
}
- if (match) {
- Shiboken::AutoDecRef tupleArgs(PyList_AsTuple(pyArgs));
- Shiboken::AutoDecRef pyMethod(PyObject_GetAttr(source->d->shared->source,
- PySide::PySideName::qtDisconnect()));
- PyObject *result = PyObject_CallObject(pyMethod, tupleArgs);
- if (result != Py_True)
- warnDisconnectFailed(slot, source->d->signature);
- return result;
+ if (slot == Py_None) { // disconnect all signal receivers by signature
+ if (isSourceDeleted(source))
+ return PyErr_Format(PyExc_RuntimeError, msgSourceDeleted);
+ const bool ok =
+ source->d->shared->source->disconnect(qSignalSignature(source).constData());
+ if (ok)
+ Py_RETURN_TRUE;
+ warnDisconnectFailed(slot, source->d->signature);
+ Py_RETURN_FALSE;
}
+ if (connection_Check(slot)) { // disconnection by connection ID
+ QMetaObject::Connection conn;
+ Shiboken::Conversions::pythonToCppCopy(metaObjConnectionConverter(), slot, &conn);
+ if (conn && QObject::disconnect(conn))
+ Py_RETURN_TRUE;
+ warnDisconnectFailed(slot, source->d->signature);
+ Py_RETURN_FALSE;
+ }
+
+ // Disconnect callable
+ if (PyCallable_Check(slot) == 0) {
+ return PyErr_Format(PyExc_TypeError,
+ "Expected signal, callable or connection id, got \"%s\"",
+ Py_TYPE(slot)->tp_name);
+ }
+
+ // try the matching signature, fall back to first
+ auto *matchedSlot = slot != Py_None ? findSignalInstanceForSlot(source, slot) : source;
+ if (isSourceDeleted(matchedSlot))
+ return PyErr_Format(PyExc_RuntimeError, msgSourceDeleted);
+
+ const auto sourceSignature = qSignalSignature(matchedSlot);
+ // disconnect all, so we need to use the c++ signature disconnect(qobj, signal, 0, 0)
+ auto *qSender = sender(matchedSlot);
+ if (slot == Py_None) {
+ const bool ok = qSender->disconnect(sourceSignature.constData(), nullptr, nullptr);
+ if (ok)
+ Py_RETURN_TRUE;
+ warnDisconnectFailed(slot, source->d->signature);
+ Py_RETURN_FALSE;
+ }
+
+ const bool ok = PySide::qobjectDisconnectCallback(qSender, sourceSignature, slot);
+ if (ok)
+ Py_RETURN_TRUE;
warnDisconnectFailed(slot, source->d->signature);
Py_RETURN_FALSE;
}
Shiboken::AutoDecRef homonymousMethod(PepExt_Type_CallDescrGet(signal->homonymousMethod,
nullptr, nullptr));
if (PyCFunction_Check(homonymousMethod.object())
- && (PyCFunction_GET_FLAGS(homonymousMethod.object()) & METH_STATIC))
+ && (PyCFunction_GetFlags(homonymousMethod.object()) & METH_STATIC))
return PyObject_Call(homonymousMethod, args, kw);
// Assumes homonymousMethod is not a static method.
// but walk through the whole mro to find a hidden method with the same name.
auto signalName = inst->d->signalName;
Shiboken::AutoDecRef name(Shiboken::String::fromCString(signalName));
- auto *mro = Py_TYPE(inst->d->shared->source)->tp_mro;
+ auto *mro = inst->d->shared->sourceType->tp_mro;
const Py_ssize_t n = PyTuple_Size(mro);
for (Py_ssize_t idx = 0; idx < n; idx++) {
static PyObject *signalInstanceCall(PyObject *self, PyObject *args, PyObject *kw)
{
auto *PySideSignal = reinterpret_cast<PySideSignalInstance *>(self);
+ if (isSourceDeleted(PySideSignal))
+ return PyErr_Format(PyExc_RuntimeError, msgSourceDeleted);
auto *hom = _getHomonymousMethod(PySideSignal);
if (!hom) {
PyErr_Format(PyExc_TypeError, "native Qt signal instance '%s' is not callable",
return nullptr;
}
- Shiboken::AutoDecRef homonymousMethod(PepExt_Type_CallDescrGet(hom, PySideSignal->d->shared->source,
- nullptr));
+ auto *source = PySide::getWrapperForQObject(sender(PySideSignal),
+ PySideSignal->d->shared->sourceType);
+ if (source == nullptr)
+ return PyErr_Format(PyExc_RuntimeError, msgSourceDeleted);
+ Shiboken::AutoDecRef homonymousMethod(PepExt_Type_CallDescrGet(hom, source, nullptr));
return PyObject_Call(homonymousMethod, args, kw);
}
Shiboken::AutoDecRef signalInstance(reinterpret_cast<PyObject *>(inst));
auto *si = reinterpret_cast<PySideSignalInstance *>(signalInstance.object());
auto shared = std::make_shared<PySideSignalInstanceShared>();
- shared->source = source;
+ shared->source = PySide::convertToQObject(source, false);
+ shared->sourceType = Py_TYPE(source);
instanceInitialize(si, key, reinterpret_cast<PySideSignal *>(value),
shared, 0);
if (PyDict_SetItem(dict, key, signalInstance) == -1)
return result;
}
-static void sourceGone(void *data)
-{
- auto *ptr = reinterpret_cast<PySideSignalInstanceSharedPtr *>(data);
- (*ptr)->deleted = true;
- delete ptr;
-}
-
static void instanceInitialize(PySideSignalInstance *self, PyObject *name,
PySideSignal *signal,
const PySideSignalInstanceSharedPtr &shared,
self->d = new PySideSignalInstancePrivate;
PySideSignalInstancePrivate *selfPvt = self->d;
selfPvt->shared = shared;
- // PYSIDE-2201: We have no reference to source. Let's take a weakref to get
- // notified when source gets deleted.
- if (index == 0) {
- auto *ptr = new PySideSignalInstanceSharedPtr(shared);
- PySide::WeakRef::create(shared->source, sourceGone, ptr);
- }
-
selfPvt->next = nullptr;
if (signal->data->signalName.isEmpty())
signal->data->signalName = Shiboken::String::toCString(name);
PySideSignalInstance *instance = PyObject_New(PySideSignalInstance,
PySideSignalInstance_TypeF());
auto shared = std::make_shared<PySideSignalInstanceShared>();
- shared->source = object;
+ shared->source = PySide::convertToQObject(object, false);
+ shared->sourceType = Py_TYPE(object);
instanceInitialize(instance, name, self, shared, 0);
- auto *sbkObj = reinterpret_cast<SbkObject *>(object);
- if (!Shiboken::Object::wasCreatedByPython(sbkObj))
- Py_INCREF(object); // PYSIDE-79: this flag was crucial for a wrapper call.
return instance;
}
return result;
}
-PySideSignalInstance *newObjectFromMethod(PyObject *source, const QList<QMetaMethod>& methodList)
+PySideSignalInstance *newObjectFromMethod(QObject *sourceQObject, PyObject *source,
+ const QList<QMetaMethod>& methodList)
{
PySideSignalInstance *root = nullptr;
PySideSignalInstance *previous = nullptr;
auto shared = std::make_shared<PySideSignalInstanceShared>();
- shared->source = source;
+ shared->source = sourceQObject;
+ shared->sourceType = Py_TYPE(source);
for (const QMetaMethod &m : methodList) {
PySideSignalInstance *item = PyObject_New(PySideSignalInstance, PySideSignalInstance_TypeF());
if (!root)
return sig1.signature.isEmpty() && !sig2.signature.isEmpty();
}
-static PyObject *buildQtCompatible(const QByteArray &signature)
-{
- const auto ba = QT_SIGNAL_SENTINEL + signature;
- return Shiboken::String::fromStringAndSize(ba, ba.size());
-}
-
void registerSignals(PyTypeObject *pyObj, const QMetaObject *metaObject)
{
using Signature = PySideSignalData::Signature;
PyObject *getObject(PySideSignalInstance *signal)
{
- return signal->d->shared->source;
+ if (auto *qSender = sender(signal))
+ return getWrapperForQObject(qSender, signal->d->shared->sourceType);
+ return nullptr;
}
const char *getSignature(PySideSignalInstance *signal)
}
#endif
} else if (PyCFunction_Check(callback)) {
- const auto *funcObj = reinterpret_cast<const PyCFunctionObject *>(callback);
- functionName = PepCFunction_GET_NAMESTR(funcObj);
- useSelf = PyCFunction_GET_SELF(funcObj) != nullptr ? 1 : 0;
- const int flags = PyCFunction_GET_FLAGS(funcObj);
+ functionName = PepCFunction_GET_NAMESTR(callback);
+ useSelf = PyCFunction_GetSelf(callback) != nullptr ? 1 : 0;
+ const int flags = PyCFunction_GetFlags(callback);
if (receiver) {
// Search for signature on metaobject
return funcName + QByteArray::number(quint64(self), 16) + QByteArray::number(quint64(func), 16);
}
// PYSIDE-1523: Handle the compiled case.
- if (PySide::isCompiledMethod(callback)) {
+ if (Shiboken::isCompiledMethod(callback)) {
// Not retaining references inline with what PyMethod_GET_(SELF|FUNC) does.
Shiboken::AutoDecRef self(PyObject_GetAttr(callback, PySide::PySideName::im_self()));
Shiboken::AutoDecRef func(PyObject_GetAttr(callback, PySide::PySideName::im_func()));
#include <sbkpython.h>
#include <basewrapper.h>
-#include <QtCore/QList>
-#include <QtCore/QMetaMethod>
+#include <QtCore/qlist.h>
+#include <QtCore/qmetaobject.h>
QT_BEGIN_NAMESPACE
struct QMetaObject;
* @param methods a list of QMetaMethod wich contains the supported signature
* @return Return a new reference to PyObject* of type PySideSignal
**/
-PYSIDE_API PySideSignalInstance *newObjectFromMethod(PyObject *source, const QList<QMetaMethod> &methods);
+PYSIDE_API PySideSignalInstance *newObjectFromMethod(QObject *sourceQObject, PyObject *source,
+ const QList<QMetaMethod> &methods);
/**
* This function initializes the Signal object by creating a PySideSignalInstance
#include <sbkpython.h>
-#include <QtCore/QByteArray>
-#include <QtCore/QList>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qobject.h>
+#include <QtCore/qpointer.h>
#include <memory>
struct PySideSignalInstanceShared
{
- PyObject *source = nullptr;
- bool deleted = false;
+ QPointer<QObject> source;
+ PyTypeObject *sourceType = nullptr;
};
using PySideSignalInstanceSharedPtr = std::shared_ptr<PySideSignalInstanceShared>;
#include "pysideslot_p.h"
#include "pysidestaticstrings.h"
-#include <shiboken.h>
-
-#include <QtCore/QMetaObject>
-#include <QtCore/QString>
+#include <autodecref.h>
+#include <basewrapper.h>
+#include <sbkstaticstrings.h>
+#include <sbkstring.h>
+#include <sbktypefactory.h>
#include <signature.h>
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qstring.h>
+
using namespace Shiboken;
struct SlotData
static const char *Slot_SignatureStrings[] = {
"PySide6.QtCore.Slot(self,*types:type,name:str=nullptr,result:type=nullptr)",
- "PySide6.QtCore.Slot.__call__(self,function:typing.Callable[[typing.Any],typing.Any])->typing.Any",
+ "PySide6.QtCore.Slot.__call__(self,function:collections.abc.Callable[...,typing.Any])->typing.Any",
nullptr}; // Sentinel
void init(PyObject *module)
/// Provide an efficient, correct PathLike interface.
PYSIDE_API QString pyPathToQString(PyObject *path);
+/// Returns whether \a method is a compiled method (Nuitka).
+/// \sa Shiboken::isCompiledMethod()
PYSIDE_API bool isCompiledMethod(PyObject *callback);
struct debugPyTypeObject
#include "pysideweakref.h"
-#include <sbkpython.h>
-#include <shiboken.h>
+#include <basewrapper.h>
+#include <sbktypefactory.h>
struct PySideCallableObject {
PyObject_HEAD
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-#ifndef __PYSIDEWEAKREF__
-#define __PYSIDEWEAKREF__
+#ifndef PYSIDEWEAKREF_H
+#define PYSIDEWEAKREF_H
#include <pysidemacros.h>
#include <sbkpython.h>
} // namespace PySide::WeakRef
-#endif
+#endif // PYSIDEWEAKREF_H
#include <sbkstaticstrings.h>
#include "basewrapper.h"
#include "autodecref.h"
+#include <helper.h>
-#include <QtCore/QDebug>
-#include <QtCore/QMetaMethod>
-#include <QtCore/QObject>
+#include <QtCore/qdebug.h>
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qobject.h>
#include <QtCore/private/qobject_p.h>
result.receiver = PySide::convertToQObject(result.self, false);
#endif
} else if (PyCFunction_Check(callback)) {
- result.self = PyCFunction_GET_SELF(callback);
+ result.self = PyCFunction_GetSelf(callback);
result.receiver = PySide::convertToQObject(result.self, false);
- } else if (PySide::isCompiledMethod(callback)) {
+ } else if (Shiboken::isCompiledMethod(callback)) {
result.self = PyObject_GetAttr(callback, Shiboken::PyName::im_self());
Py_DECREF(result.self);
result.receiver = PySide::convertToQObject(result.self, false);
QObject *receiver, QMetaMethod slot,
Qt::ConnectionType type)
{
- return qobjectConnect(source, signal.methodSignature().constData(),
- receiver, slot.methodSignature().constData(), type);
+ PySide::SignalManager::registerMetaMethod(receiver,
+ slot.methodSignature().constData(),
+ slot.methodType());
+ return QObject::connect(source, signal, receiver, slot, type);
}
QMetaObject::Connection qobjectConnectCallback(QObject *source, const char *signal,
if (signalIndex == -1)
return false;
- if (!disconnectSlot(source, signalIndex, callback))
- return false;
+ if (!disconnectSlot(source, signalIndex, callback)) {
+ // PYSIDE-3020: Check for disconnecting a string-based connection by passing a callable?
+ auto receiver = getReceiver(metaObject->method(signalIndex), callback);
+ if (receiver.receiver == nullptr || receiver.slotIndex == -1
+ || !QMetaObject::disconnect(source, signalIndex,
+ receiver.receiver, receiver.slotIndex)) {
+ return false;
+ }
+ }
const QMetaMethod signalMethod = metaObject->method(signalIndex);
static_cast<FriendlyQObject *>(source)->disconnectNotify(signalMethod);
#include <sbkpython.h>
-#include <QtCore/QMetaObject>
+#include <QtCore/qmetaobject.h>
QT_FORWARD_DECLARE_CLASS(QObject)
QT_FORWARD_DECLARE_CLASS(QMetaMethod)
#include <sbkstaticstrings.h>
#include <sbkerrors.h>
-#include <QtCore/QCoreApplication>
-#include <QtCore/QByteArrayView>
-#include <QtCore/QDebug>
-#include <QtCore/QHash>
-#include <QtCore/QScopedPointer>
-#include <QtCore/QTimerEvent>
-
+#include <QtCore/qbytearrayview.h>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qcoreevent.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qmetatype.h>
+#include <QtCore/qscopedpointer.h>
+
+#include <climits>
#include <memory>
+#include <utility>
using namespace Qt::StringLiterals;
static PyObject *metaObjectAttr = nullptr;
+static int pyObjectWrapperMetaTypeId = QMetaType::UnknownType;
+
static void destroyMetaObject(PyObject *obj)
{
void *ptr = PyCapsule_GetPointer(obj, nullptr);
Py_XINCREF(m_me);
}
+PyObjectWrapper::PyObjectWrapper(PyObjectWrapper &&other) noexcept
+ : m_me{std::exchange(other.m_me, nullptr)}
+{
+}
+
+PyObjectWrapper &PyObjectWrapper::operator=(PyObjectWrapper &&other) noexcept
+{
+ m_me = std::exchange(other.m_me, nullptr);
+ return *this;
+}
+
PyObjectWrapper::~PyObjectWrapper()
{
// Check that Python is still initialized as sometimes this is called by a static destructor
return m_me;
}
+int PyObjectWrapper::metaTypeId()
+{
+ return pyObjectWrapperMetaTypeId;
+}
int PyObjectWrapper::toInt() const
{
return in;
}
-};
+PYSIDE_API QDebug operator<<(QDebug debug, const PyObjectWrapper &myObj)
+{
+ QDebugStateSaver saver(debug);
+ debug.noquote();
+ debug.nospace();
+ // Do not repeat the type name as it is typically called from the QVariant debug
+ // operator, which outputs the type.
+ debug << '<';
+ if (PyObject *ob = myObj) {
+ const auto refs = Py_REFCNT(ob);
+ debug << Py_TYPE(ob)->tp_name << " at " << ob;
+ if (refs == UINT_MAX) // _Py_IMMORTAL_REFCNT
+ debug << ", immortal";
+ else
+ debug << ", refs=" << refs;
+ } else {
+ debug << '0';
+ }
+ debug << '>';
+ return debug;
+}
+
+} // namespace PySide
using namespace PySide;
SignalManager::QmlMetaCallErrorHandler
SignalManagerPrivate::m_qmlMetaCallErrorHandler = nullptr;
-static void PyObject_PythonToCpp_PyObject_PTR(PyObject *pyIn, void *cppOut)
-{
- *reinterpret_cast<PyObject **>(cppOut) = pyIn;
-}
-static PythonToCppFunc is_PyObject_PythonToCpp_PyObject_PTR_Convertible(PyObject * /* pyIn */)
+static PyObject *CopyCppToPythonPyObject(const void *cppIn)
{
- return PyObject_PythonToCpp_PyObject_PTR;
-}
-static PyObject *PyObject_PTR_CppToPython_PyObject(const void *cppIn)
-{
- auto *pyOut = reinterpret_cast<PyObject *>(const_cast<void *>(cppIn));
- if (pyOut)
- Py_INCREF(pyOut);
+ const auto *wrapper = reinterpret_cast<const PyObjectWrapper *>(cppIn);
+ PyObject *pyOut = *wrapper;
+ Py_XINCREF(pyOut);
return pyOut;
}
using namespace Shiboken;
// Register PyObject type to use in queued signal and slot connections
- qRegisterMetaType<PyObjectWrapper>("PyObject");
+ pyObjectWrapperMetaTypeId = qRegisterMetaType<PyObjectWrapper>("PyObject");
// Register QVariant(enum) conversion to QVariant(int)
QMetaType::registerConverter<PyObjectWrapper, int>(&PyObjectWrapper::toInt);
- SbkConverter *converter = Shiboken::Conversions::createConverter(&PyBaseObject_Type, nullptr);
- Shiboken::Conversions::setCppPointerToPythonFunction(converter, PyObject_PTR_CppToPython_PyObject);
- Shiboken::Conversions::setPythonToCppPointerFunctions(converter, PyObject_PythonToCpp_PyObject_PTR, is_PyObject_PythonToCpp_PyObject_PTR_Convertible);
+ // Register a shiboken converter for PyObjectWrapper->Python (value conversion).
+ // Python->PyObjectWrapper is not registered since the converters do not work for
+ // non-SbkObject types (falling back to plain pointer pass through).
+ // This conversion needs to be done manually via QVariant.
+ SbkConverter *converter = Shiboken::Conversions::createConverter(&PyBaseObject_Type,
+ CopyCppToPythonPyObject);
Shiboken::Conversions::registerConverterName(converter, "PyObject");
Shiboken::Conversions::registerConverterName(converter, "object");
Shiboken::Conversions::registerConverterName(converter, "PyObjectWrapper");
// because the object can be deleted inside the called slot.
if (gil == nullptr)
- gil.reset(new Shiboken::GilState);
+ gil = std::make_unique<Shiboken::GilState>();
if (PyErr_Occurred())
handleMetaCallError(object, &result);
const char *returnType /* = nullptr */,
void **args, PyObject *pyCallable)
{
+ using SpecificConverter = Shiboken::Conversions::SpecificConverter;
const qsizetype argsSize = paramTypes.size();
Shiboken::AutoDecRef preparedArgs(PyTuple_New(argsSize));
for (qsizetype i = 0; i < argsSize; ++i) {
void *data = args[i + 1];
- auto param = paramTypes.at(i);
- Shiboken::Conversions::SpecificConverter converter(param.constData());
+ const auto ¶m = paramTypes.at(i);
+ SpecificConverter converter(param.constData());
if (!converter.isValid())
return CallResult::CallArgumentError + int(i);
- PyTuple_SetItem(preparedArgs, i, converter.toPython(data));
+ // Only pointer conversion available for const-ref - add indirection
+ const bool valueToPtr = converter.conversionType() == SpecificConverter::PointerConversion
+ && !param.endsWith('*') && param != "PyObject"_ba;
+ auto *src = valueToPtr ? &data : data;
+ PyTuple_SetItem(preparedArgs, i, converter.toPython(src));
}
QScopedPointer<Shiboken::Conversions::SpecificConverter> retConverter;
#include <sbkpython.h>
#include <shibokenmacros.h>
-#include <QtCore/QMetaMethod>
+#include <QtCore/qmetaobject.h>
#include <optional>
QT_FORWARD_DECLARE_CLASS(QDataStream)
+QT_FORWARD_DECLARE_CLASS(QDebug)
namespace PySide
{
class PYSIDE_API PyObjectWrapper
{
public:
- PyObjectWrapper(PyObjectWrapper&&) = delete;
- PyObjectWrapper& operator=(PyObjectWrapper &&) = delete;
PyObjectWrapper();
explicit PyObjectWrapper(PyObject* me);
PyObjectWrapper(const PyObjectWrapper &other);
PyObjectWrapper& operator=(const PyObjectWrapper &other);
+ PyObjectWrapper(PyObjectWrapper&&) noexcept;
+ PyObjectWrapper &operator=(PyObjectWrapper &&) noexcept;
void reset(PyObject *o);
// The proper fix would be to associate PyObjectWrapper to the corresponding C++ Enum.
int toInt() const;
+ static int metaTypeId();
+
private:
PyObject* m_me;
};
PYSIDE_API QDataStream &operator<<(QDataStream& out, const PyObjectWrapper& myObj);
PYSIDE_API QDataStream &operator>>(QDataStream& in, PyObjectWrapper& myObj);
+PYSIDE_API QDebug operator<<(QDebug debug, const PyObjectWrapper &myObj);
class PYSIDE_API SignalManager
{
public:
Q_DISABLE_COPY_MOVE(SignalManager)
+ ~SignalManager() = default;
using QmlMetaCallErrorHandler = std::optional<int>(*)(QObject *object);
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
+find_package(Qt6 COMPONENTS Core CorePrivate Qml QmlPrivate)
+
set(libpysideqml_libraries Qt::Core Qt::CorePrivate Qt::Qml Qt::QmlPrivate)
set(libpysideqml_HEADERS # installed below
#include "pysideqmluncreatable.h"
#include "pysideqmlmetacallerror_p.h"
-#include <QtQml/QQmlPropertyMap>
-#include <QtQml/QQmlComponent>
+#include <QtQml/qqmlpropertymap.h>
+#include <QtQml/qqmlcomponent.h>
#include <signalmanager.h>
#include <pyside_p.h>
#include <pysideclassdecorator_p.h>
-#include <shiboken.h>
-#include <signature.h>
+#include <autodecref.h>
+#include <gilstate.h>
+#include <sbkconverter.h>
#include <sbkstring.h>
+#include <sbktypefactory.h>
+#include <signature.h>
#include <QtQml/qqml.h>
#include <pyside_p.h>
#include <pysideclassdecorator_p.h>
-#include <shiboken.h>
-#include <signature.h>
+#include <autodecref.h>
+#include <gilstate.h>
+#include <sbkconverter.h>
#include <sbkstring.h>
+#include <sbktypefactory.h>
+#include <signature.h>
#include <QtQml/qqml.h>
#include <signalmanager.h>
#include <pysideclassdecorator_p.h>
-#include <shiboken.h>
-#include <signature.h>
+#include <gilstate.h>
+#include <sbkconverter.h>
#include <sbkstring.h>
+#include <sbktypefactory.h>
+#include <signature.h>
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
// The QmlForeign decorator modifies QmlElement to create a different type
// QmlElement.
#include "pysideqmllistproperty_p.h"
#include "pysideqmlregistertype_p.h"
-#include <shiboken.h>
+#include <autodecref.h>
+#include <gilstate.h>
#include <pep384ext.h>
+#include <sbkconverter.h>
#include <signature.h>
+#include <sbkstring.h>
+#include <sbktypefactory.h>
#include <pysideproperty.h>
#include <pysideproperty_p.h>
-#include <QtCore/QObject>
-#include <QtQml/QQmlListProperty>
+#include <QtCore/qobject.h>
+#include <QtQml/qqmllist.h>
// This is the user data we store in the property.
class QmlListPropertyPrivate : public PySidePropertyPrivate
static const char *PropertyList_SignatureStrings[] = {
"PySide6.QtQml.ListProperty(self,type:type,"
- "append:typing.Optional[typing.Callable[...,typing.Any]]=None,"
- "at:typing.Optional[typing.Callable[...,typing.Any]]=None,"
- "clear:typing.Optional[typing.Callable[...,typing.Any]]=None,"
- "count:typing.Optional[typing.Callable[...,typing.Any]]=None)",
+ "append:typing.Optional[collections.abc.Callable[...,typing.Any]]=None,"
+ "at:typing.Optional[collections.abc.Callable[...,typing.Any]]=None,"
+ "clear:typing.Optional[collections.abc.Callable[...,typing.Any]]=None,"
+ "count:typing.Optional[collections.abc.Callable[...,typing.Any]]=None)",
nullptr // Sentinel
};
# undef copysign
#endif
-#include <QtCore/QObject>
-#include <QtCore/QString>
+#include <QtCore/qobject.h>
+#include <QtCore/qstring.h>
-#include <QtQml/QQmlEngine>
-#include <QtQml/QQmlListProperty>
+#include <QtQml/qqmlengine.h>
+#include <QtQml/qqmllist.h>
#if __has_include (<private/qv4engine_p.h>)
# define QML_PRIVATE_API_SUPPORT
#include <pysideclassdecorator_p.h>
#include <pysideqmlregistertype_p.h>
-#include <shiboken.h>
+#include <sbktypefactory.h>
#include <signature.h>
class PySideQmlNamedElementPrivate : public PySide::ClassDecorator::StringDecoratorPrivate
#include <optional>
// shiboken
-#include <shiboken.h>
+#include <autodecref.h>
+#include <sbkconverter.h>
#include <sbkstring.h>
+#include <sbktypefactory.h>
// pyside
-#include <pyside.h>
#include <pysideqobject.h>
#include <pysideclassinfo.h>
#include <pyside_p.h>
-#include <QtCore/QMutex>
-#include <QtCore/QTypeRevision>
+#include <QtCore/qmutex.h>
+#include <QtCore/qtyperevision.h>
#include <QtQml/qqml.h>
-#include <QtQml/QJSValue>
-#include <QtQml/QQmlListProperty>
+#include <QtQml/qjsvalue.h>
+#include <QtQml/qqmllist.h>
#include <private/qqmlmetatype_p.h>
#include <private/qmetaobjectbuilder_p.h>
#include <sbkpython.h>
-#include <QtCore/QByteArray>
+#include <QtCore/qbytearray.h>
PyTypeObject *qObjectType();
#include "pysideqmltypeinfo_p.h"
-#include <QtCore/QDebug>
-#include <QtCore/QHash>
+#include <QtCore/qdebug.h>
+#include <QtCore/qhash.h>
#include <algorithm>
#include <sbkpython.h>
-#include <QtCore/QByteArray>
-#include <QtCore/QFlags>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qflags.h>
#include <memory>
#include <pysideclassdecorator_p.h>
#include <pysideclassinfo.h>
-#include <shiboken.h>
-#include <signature.h>
#include <sbkcppstring.h>
+#include <sbktypefactory.h>
+#include <signature.h>
#include <QtCore/qbytearray.h>
#include <private/qmetaobjectbuilder_p.h>
#include <sbkpython.h>
-#include <QtCore/QByteArray>
+#include <QtCore/qbytearray.h>
QT_FORWARD_DECLARE_CLASS(QMetaObjectBuilder)
--- /dev/null
+# Copyright (C) 2025 Ford Motor Company
+# SPDX-License-Identifier: BSD-3-Clause
+
+if (NOT CMAKE_MINIMUM_REQUIRED_VERSION)
+ cmake_minimum_required(VERSION 3.18)
+ cmake_policy(VERSION 3.18)
+endif()
+
+project(libpysideremoteobjects LANGUAGES CXX)
+
+if (NOT libpyside_SOURCE_DIR) # Building standalone
+ message(STATUS "Building standalone. Setting C++ standard and build type.")
+ set(CMAKE_CXX_STANDARD 17)
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
+ if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE Release)
+ endif()
+ find_package(Python3 REQUIRED COMPONENTS Interpreter Development)
+ find_package(Shiboken6 REQUIRED)
+ find_package(libpyside REQUIRED)
+ get_target_property(pyside6_SOURCE_DIR PySide6::pyside6 INTERFACE_INCLUDE_DIRECTORIES)
+endif()
+
+find_package(Qt6 REQUIRED COMPONENTS Core RepParser RemoteObjects RemoteObjectsPrivate)
+
+set(libpysideremoteobjects_HEADERS
+ pysidecapsulemethod_p.h
+ pysidedynamicclass_p.h
+ pysidedynamiccommon_p.h
+ pysidedynamicenum_p.h
+ pysidedynamicpod_p.h
+ pysiderephandler_p.h
+)
+
+set(libpysideremoteobjects_SRC
+ pysiderephandler.cpp
+ pysidecapsulemethod.cpp
+ pysidedynamiccommon.cpp
+ pysidedynamicclass.cpp
+ pysidedynamicpod.cpp
+ pysidedynamicenum.cpp
+ ${libpysideremoteobjects_HEADERS}
+)
+
+list(GET Qt6RepParser_INCLUDE_DIRS 0 REPPARSER_DIR)
+
+include(QtTargetHelpers)
+include(QtTestHelpers)
+include(QtLalrHelpers)
+add_library(pyside6remoteobjects STATIC ${libpysideremoteobjects_SRC})
+
+target_include_directories(pyside6remoteobjects PRIVATE
+ ${REPPARSER_DIR}
+ ${Qt${QT_VERSION_MAJOR}Core_PRIVATE_INCLUDE_DIRS}
+ ${Qt${QT_MAJOR_VERSION}RemoteObjects_INCLUDE_DIRS}
+ ${Qt${QT_MAJOR_VERSION}RemoteObjects_PRIVATE_INCLUDE_DIRS}
+ ${pyside6_SOURCE_DIR} # Added internally by the create_pyside_module function
+ ${SHIBOKEN_INCLUDE_DIR}
+ ${libpyside_SOURCE_DIR}
+ ${SHIBOKEN_PYTHON_INCLUDE_DIR}
+ ${CMAKE_CURRENT_BINARY_DIR} # Include the component-specific build directory
+)
+
+target_link_libraries(pyside6remoteobjects PRIVATE
+ Shiboken6::libshiboken # Added internally by the create_pyside_module function
+ Qt6::Core
+ Qt6::RemoteObjectsPrivate
+)
+
+qt_process_qlalr(
+ pyside6remoteobjects
+ "${REPPARSER_DIR}/parser.g"
+ ""
+)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D QT_NO_CAST_FROM_ASCII -D QT_NO_CAST_TO_ASCII")
+
+#
+# install stuff
+#
+
+install(FILES ${libpysideremoteobjects_HEADERS}
+ DESTINATION include/${BINDING_NAME}${pyside6remoteobjects_SUFFIX})
+
+install(TARGETS pyside6remoteobjects EXPORT PySide6RemoteObjectsTargets
+ LIBRARY DESTINATION "${LIB_INSTALL_DIR}"
+ ARCHIVE DESTINATION "${LIB_INSTALL_DIR}"
+ RUNTIME DESTINATION bin)
--- /dev/null
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "pysidecapsulemethod_p.h"
+
+extern "C"
+{
+
+// This struct is used for both CapsuleMethod and CapsuleProperty
+struct CapsuleDescriptor
+{
+ PyTypeObject base;
+ PyObject *capsule;
+ PyMethodDef methodDef;
+
+ void configure(PyObject *capsule, PyMethodDef *method)
+ {
+ this->capsule = capsule;
+ Py_INCREF(capsule);
+ // We make a copy of the input name and doc strings so they can be temporary on
+ // the input.
+ if (method->ml_name)
+ methodDef.ml_name = strdup(method->ml_name);
+ if (method->ml_doc)
+ methodDef.ml_doc = strdup(method->ml_doc);
+ methodDef.ml_meth = method->ml_meth;
+ methodDef.ml_flags = method->ml_flags;
+ }
+};
+
+static PyObject *CapsuleDescriptor_tp_new(PyTypeObject *type, PyObject * /* args */, PyObject * /* kwds */);
+static void CapsuleDescriptor_free(PyObject *self);
+static PyObject *CapsuleMethod_descr_get(PyObject *self, PyObject *instance, PyObject * /* owner */);
+static PyObject *CapsuleProperty_descr_get(PyObject *self, PyObject *instance, PyObject * /* owner */);
+static int CapsuleProperty_descr_set(PyObject *self, PyObject *instance, PyObject * /* owner */);
+
+/**
+ * We are creating two related types, CapsuleMethod and CapsuleProperty, that are
+ * used to enable lambda-like behavior. The difference is in usage, where
+ * CapsuleMethod's __get__ function returns a Callable (i.e., method-like usage:
+ * obj.capsuleMethodName(args)) and only supports the __get__ method.
+ * CapsuleProperty on the other hand is used for properties, and supports both
+ * __get__ and __set__ methods (i.e., obj.capsulePropertyName = value or val =
+ * obj.capsulePropertyName).
+ */
+static PyTypeObject *createCapsuleMethodType()
+{
+ PyType_Slot CapsuleMethodType_slots[] = {
+ {Py_tp_new, reinterpret_cast<void *>(CapsuleDescriptor_tp_new)},
+ {Py_tp_descr_get, reinterpret_cast<void *>(CapsuleMethod_descr_get)},
+ {Py_tp_free, reinterpret_cast<void *>(CapsuleDescriptor_free)},
+ {0, nullptr}
+ };
+
+ PyType_Spec CapsuleMethodType_spec = {
+ "2:PySide6.QtRemoteObjects.CapsuleMethod",
+ sizeof(CapsuleDescriptor),
+ 0,
+ Py_TPFLAGS_DEFAULT,
+ CapsuleMethodType_slots};
+
+ PyObject *type = PyType_FromSpec(&CapsuleMethodType_spec);
+ if (!type) {
+ PyErr_Print();
+ return nullptr;
+ }
+ return reinterpret_cast<PyTypeObject*>(type);
+}
+
+PyTypeObject *CapsuleMethod_TypeF(void)
+{
+ static auto *type = createCapsuleMethodType();
+ return type;
+}
+
+static PyTypeObject *createCapsulePropertyType(bool isWritable)
+{
+ PyType_Slot WritablePropertyType_slots[] = {
+ {Py_tp_new, reinterpret_cast<void *>(CapsuleDescriptor_tp_new)},
+ {Py_tp_descr_get, reinterpret_cast<void *>(CapsuleProperty_descr_get)},
+ {Py_tp_descr_set, reinterpret_cast<void *>(CapsuleProperty_descr_set)},
+ {Py_tp_free, reinterpret_cast<void *>(CapsuleDescriptor_free)},
+ {0, nullptr}
+ };
+
+ PyType_Slot ReadOnlyPropertyType_slots[] = {
+ {Py_tp_new, reinterpret_cast<void *>(CapsuleDescriptor_tp_new)},
+ {Py_tp_descr_get, reinterpret_cast<void *>(CapsuleProperty_descr_get)},
+ {Py_tp_free, reinterpret_cast<void *>(CapsuleDescriptor_free)},
+ {0, nullptr}
+ };
+
+ PyType_Spec CapsulePropertyType_spec = {
+ "2:PySide6.QtRemoteObjects.CapsuleProperty",
+ sizeof(CapsuleDescriptor),
+ 0,
+ Py_TPFLAGS_DEFAULT,
+ isWritable ? WritablePropertyType_slots : ReadOnlyPropertyType_slots};
+
+ PyObject *type = PyType_FromSpec(&CapsulePropertyType_spec);
+ if (!type) {
+ PyErr_Print();
+ return nullptr;
+ }
+ return reinterpret_cast<PyTypeObject*>(type);
+}
+
+PyTypeObject *CapsuleProperty_TypeF(bool isWritable=false)
+{
+ if (isWritable) {
+ static auto *type = createCapsulePropertyType(true);
+ return type;
+ }
+ static auto *type = createCapsulePropertyType(false);
+ return type;
+}
+
+static PyObject *CapsuleDescriptor_tp_new(PyTypeObject *type, PyObject * /* args */, PyObject * /* kwds */)
+{
+ auto *self = reinterpret_cast<CapsuleDescriptor *>(PyType_GenericAlloc(type, 0));
+ if (self != nullptr) {
+ self->capsule = nullptr;
+ self->methodDef = {nullptr, nullptr, METH_NOARGS, nullptr}; // Initialize methodDef
+ }
+ return reinterpret_cast<PyObject *>(self);
+}
+
+static void CapsuleDescriptor_free(PyObject *self)
+{
+ auto *d = reinterpret_cast<CapsuleDescriptor *>(self);
+ Py_XDECREF(d->capsule);
+ free(const_cast<char*>(d->methodDef.ml_name));
+ free(const_cast<char*>(d->methodDef.ml_doc));
+}
+
+static PyObject *CapsuleMethod_descr_get(PyObject *self, PyObject *instance, PyObject * /* owner */)
+{
+ if (instance == nullptr) {
+ // Return the descriptor object if accessed from the class
+ Py_INCREF(self);
+ return self;
+ }
+
+ auto *d = reinterpret_cast<CapsuleDescriptor *>(self);
+ CapsuleDescriptorData *data = new CapsuleDescriptorData{instance, d->capsule};
+ PyObject *payload = PyCapsule_New(data, "Payload", [](PyObject *capsule) {
+ delete reinterpret_cast<CapsuleDescriptorData *>(PyCapsule_GetPointer(capsule, "Payload"));
+ });
+ if (!payload)
+ return nullptr;
+
+ Py_INCREF(payload);
+ return PyCFunction_New(&d->methodDef, payload);
+}
+
+bool add_capsule_method_to_type(PyTypeObject *type, PyMethodDef *method, PyObject *capsule)
+{
+ if (PyType_Ready(type) < 0) {
+ PyErr_Print();
+ return false;
+ }
+ auto *descriptor = reinterpret_cast<CapsuleDescriptor *>(
+ PyObject_CallObject(reinterpret_cast<PyObject *>(CapsuleMethod_TypeF()), nullptr));
+ if (!descriptor) {
+ PyErr_Print();
+ return false;
+ }
+ descriptor->configure(capsule, method);
+
+ auto *descr = reinterpret_cast<PyObject *>(descriptor);
+ if (PyObject_SetAttrString(reinterpret_cast<PyObject *>(type), method->ml_name, descr) < 0) {
+ PyErr_Print();
+ return false;
+ }
+ return true;
+}
+
+static PyObject *CapsuleProperty_descr_get(PyObject *self, PyObject *instance, PyObject * /* owner */)
+{
+ if (instance == nullptr) {
+ // Return the descriptor object if accessed from the class
+ Py_INCREF(self);
+ return self;
+ }
+
+ auto *d = reinterpret_cast<CapsuleDescriptor *>(self);
+ CapsuleDescriptorData *data = new CapsuleDescriptorData{instance, d->capsule};
+ PyObject *payload = PyCapsule_New(data, "Payload", [](PyObject *capsule) {
+ delete reinterpret_cast<CapsuleDescriptorData *>(PyCapsule_GetPointer(capsule, "Payload"));
+ });
+ if (!payload)
+ return nullptr;
+
+ return PyObject_CallFunctionObjArgs(PyCFunction_New(&d->methodDef, payload), nullptr);
+}
+
+static int CapsuleProperty_descr_set(PyObject *self, PyObject *instance, PyObject *value)
+{
+ auto *d = reinterpret_cast<CapsuleDescriptor *>(self);
+ CapsuleDescriptorData *data = new CapsuleDescriptorData{instance, d->capsule};
+ PyObject *payload = PyCapsule_New(data, "Payload", [](PyObject *capsule) {
+ delete reinterpret_cast<CapsuleDescriptorData *>(PyCapsule_GetPointer(capsule, "Payload"));
+ });
+ if (!payload)
+ return -1;
+
+ Py_INCREF(payload);
+ PyObject *result = PyObject_CallFunctionObjArgs(PyCFunction_New(&d->methodDef, payload),
+ value, nullptr);
+ if (!result)
+ return -1;
+
+ Py_DECREF(result);
+ return 0;
+}
+
+// Returns a new CapsuleProperty descriptor object for use with PySideProperty
+PyObject *make_capsule_property(PyMethodDef *method, PyObject *capsule, bool isWritable)
+{
+ auto *type = CapsuleProperty_TypeF(isWritable);
+ auto *descriptor = PyObject_CallObject(reinterpret_cast<PyObject *>(type), nullptr);
+ if (!descriptor)
+ return nullptr;
+
+ reinterpret_cast<CapsuleDescriptor*>(descriptor)->configure(capsule, method);
+
+ return descriptor;
+}
+
+} // extern "C"
--- /dev/null
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef PYSIDE_CAPSULEMETHOD_P_H
+#define PYSIDE_CAPSULEMETHOD_P_H
+
+#include <sbkpython.h>
+
+extern "C"
+{
+
+/**
+ * This code is needed to solve, in C++ and adhering to the stable API,
+ * creating what are in effect lambda functions as instance methods on custom
+ * types. The goal is to be able to add methods to a dynamic type. If the .rep
+ * file defines a slot `mySlot`, it need to be added to the dynamic type. For
+ * Source types, this should be an abstract method that raises a
+ * NotImplementedError unless defined in the Python subclass. For Replica
+ * types, this should include an implementation that forwards the request
+ * through the underlying QRemoteObjectReplica instance.
+ *
+ * The stable API doesn't currently provide a way define a method that can
+ * receive both the `self`, `args`, and runtime (but constant per method, i.e.,
+ * lambda like) data using Py_tp_methods. Possibly post 3.13 when METH_METHOD is
+ * part of the stable API. But for now, it is not.
+ *
+ * The solution is to create a custom descriptor
+ * (https://docs.python.org/3/howto/descriptor.html) that can hold the runtime
+ * data and then when called, will return a PyCFunction_New generated PyObject
+ * that is passed both class instance `self` and the runtime data (a PyCapsule)
+ * together as a tuple as a new `self` for the method. The static method
+ * definition needs to expect and handle this, but when combined in C++, we can
+ * define a single handler that receives both the original `self` of the instance
+ * and the runtime capsule with data for handling.
+ */
+
+/**
+ * The CapsuleDescriptorData struct is what will be passed as the pseudo `self`
+ * from a CapsuleMethod or CapsuleProperty to the associated handler method. The
+ * handler method (which should look like a standard PyMethodDef method) should
+ * parse it into the payload (the "lambda variables") and the actual instance
+ * (the "self").
+ */
+struct CapsuleDescriptorData
+{
+ PyObject *self;
+ PyObject *payload;
+};
+
+/**
+ * The new type defining a descriptor that stores a PyCapsule. This is used to
+ * store the runtime data, with the __get__ method returning a new Callable.
+ */
+PyTypeObject *CapsuleMethod_TypeF(void);
+
+/**
+ * The new type defining a descriptor that stores a PyCapsule. This is used to
+ * store the runtime data, with the __get__ (and __set__ if isWritable) providing
+ * property behavior.
+ */
+PyTypeObject *CapsuleProperty_TypeF(bool isWritable);
+
+/**
+ * Add a capsule method (a descriptor) to a type. This will create a new capsule
+ * method descriptor and add it as an attribute to the type, using the given name.
+ *
+ * A single handle can then respond to what appear to be distinct methods on the
+ * type, but using the runtime data (from the capsule) when handling each call.
+ *
+ * @param type The type to attach the created descriptor to.
+ * @param method The method definition to associate with the descriptor.
+ * The name of the method will be used as the attribute name.
+ * @param capsule The capsule to store in the descriptor.
+ * @return True if the descriptor was added successfully, false otherwise.
+ */
+bool add_capsule_method_to_type(PyTypeObject *type, PyMethodDef *method,
+ PyObject *capsule);
+
+/**
+ * Make a new CapsuleProperty type.
+ */
+PyObject *make_capsule_property(PyMethodDef *method, PyObject *capsule,
+ bool isWritable = false);
+
+} // extern "C"
+
+#endif // PYSIDE_CAPSULEMETHOD_P_H
--- /dev/null
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+// Workaround to access protected functions. PySide builds with this, but
+// since this is now a separate library, we need to add it too.
+
+#include "pysidedynamiccommon_p.h"
+#include "pysidedynamicclass_p.h"
+#include "pysidecapsulemethod_p.h"
+#include "pysiderephandler_p.h"
+
+#include <basewrapper.h>
+#include <sbkconverter.h>
+#include <sbkstring.h>
+
+#include <pyside_p.h>
+#include <pysideproperty.h>
+#include <pysideqobject.h>
+#include <pysidesignal.h>
+#include <pysideutils.h>
+
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qvariantlist.h>
+
+#include <QtRemoteObjects/qremoteobjectpendingcall.h>
+#include <QtRemoteObjects/qremoteobjectreplica.h>
+
+using namespace Shiboken;
+
+class FriendlyReplica : public QRemoteObjectReplica
+{
+public:
+ using QRemoteObjectReplica::send;
+ using QRemoteObjectReplica::setProperties;
+ using QRemoteObjectReplica::propAsVariant;
+ using QRemoteObjectReplica::sendWithReply;
+};
+
+extern "C"
+{
+
+PyObject *propertiesAttr()
+{
+ static PyObject *const s = Shiboken::String::createStaticString("__PROPERTIES__");
+ return s;
+}
+
+struct SourceDefs
+{
+ static PyTypeObject *getSbkType()
+ {
+ static PyTypeObject *sbkType =
+ Shiboken::Conversions::getPythonTypeObject("QObject");
+ return sbkType;
+ }
+
+ static PyObject *getBases()
+ {
+ static PyObject *bases = PyTuple_Pack(1, getSbkType());
+ return bases;
+ }
+
+ static const char *getTypePrefix()
+ {
+ return "2:PySide6.QtRemoteObjects.DynamicSource.";
+ }
+
+ static int tp_init(PyObject *self, PyObject *args, PyObject *kwds)
+ {
+ static initproc initFunc = reinterpret_cast<initproc>(PepType_GetSlot(getSbkType(), Py_tp_init));
+ int res = initFunc(self, args, kwds);
+ if (res < 0) {
+ PyErr_Print();
+ return res;
+ }
+
+ // Get the properties from the type
+ PyTypeObject *type = Py_TYPE(self);
+ auto *pyProperties = PyObject_GetAttr(reinterpret_cast<PyObject *>(type), propertiesAttr());
+ if (!pyProperties) {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to get properties from type");
+ return -1;
+ }
+ // Add a copy of the properties to the object
+ auto *propPtr = reinterpret_cast<QVariantList *>(PyCapsule_GetPointer(pyProperties, nullptr));
+ auto *propertiesCopy = new QVariantList(*propPtr);
+ PyObject *capsule = PyCapsule_New(propertiesCopy, nullptr, [](PyObject *capsule) {
+ delete reinterpret_cast<QVariantList *>(PyCapsule_GetPointer(capsule, nullptr));
+ });
+ PyObject_SetAttr(self, propertiesAttr(), capsule);
+ Py_DECREF(capsule);
+ return res;
+ }
+
+ static PyObject *capsule_method_handler(PyObject *payload, PyObject *args)
+ {
+ auto *methodData = reinterpret_cast<CapsuleDescriptorData *>(PyCapsule_GetPointer(payload,
+ "Payload"));
+ if (!methodData) {
+ PyErr_SetString(PyExc_RuntimeError, "Invalid call to dynamic method. Missing payload.");
+ return nullptr;
+ }
+ PyObject *self = methodData->self;
+ if (PyCapsule_IsValid(methodData->payload, "PropertyCapsule")) {
+ // Handle property getter/setter against our hidden properties attribute
+ auto *capsule = PyCapsule_GetPointer(methodData->payload, "PropertyCapsule");
+ if (capsule) {
+ auto *ob_dict = SbkObject_GetDict_NoRef(self);
+ auto *propPtr = PyCapsule_GetPointer(PyDict_GetItem(ob_dict, propertiesAttr()),
+ nullptr);
+ auto *currentProperties = reinterpret_cast<QVariantList *>(propPtr);
+ auto *callData = reinterpret_cast<PropertyCapsule *>(capsule);
+ if (callData->indexInObject < 0
+ || callData->indexInObject >= currentProperties->size()) {
+ PyErr_Format(PyExc_RuntimeError, "Unknown property method: %s",
+ callData->name.constData());
+ return nullptr;
+ }
+ const QVariant ¤tVariant = currentProperties->at(callData->indexInObject);
+
+ // Handle getter
+ if (PyTuple_Size(args) == 0)
+ return toPython(currentVariant);
+
+ // Handle setter
+ if (PyTuple_Size(args) != 1) {
+ PyErr_SetString(PyExc_TypeError, "Property setter takes exactly one argument");
+ return nullptr;
+ }
+ Conversions::SpecificConverter converter(currentVariant.metaType().name());
+ QVariant variant{currentVariant.metaType()};
+ auto metaType = currentVariant.metaType();
+ if (metaType.flags().testFlag(QMetaType::IsEnumeration)) {
+ converter.toCpp(PyTuple_GetItem(args, 0), variant.data());
+ variant.convert(metaType);
+ } else {
+ converter.toCpp(PyTuple_GetItem(args, 0), variant.data());
+ }
+ if (PyErr_Occurred()) // POD conversion can produce an error
+ return nullptr;
+ if (variant == currentVariant)
+ Py_RETURN_NONE;
+
+ currentProperties->replace(callData->indexInObject, variant);
+ // Get the QMetaObject and emit the property changed signal if there is one
+ const auto *metaObject = PySide::retrieveMetaObject(self);
+ auto metaProperty = metaObject->property(callData->propertyIndex);
+ if (metaProperty.hasNotifySignal()) {
+ // We know our custom types don't have multiple cpp objects
+ void *cptr = reinterpret_cast<SbkObject *>(self)->d->cptr[0];
+ auto *qObject = reinterpret_cast<QObject *>(cptr);
+ void *_args[] = {nullptr, variant.data()};
+ QMetaObject::activate(qObject, metaProperty.notifySignalIndex(), _args);
+ }
+ Py_RETURN_NONE;
+ }
+ }
+ if (PyCapsule_IsValid(methodData->payload, "MethodCapsule")) {
+ auto *capsule = PyCapsule_GetPointer(methodData->payload, "MethodCapsule");
+ auto *callData = reinterpret_cast<MethodCapsule *>(capsule);
+ if (callData->name.startsWith("push") && callData->name.size() > 4) {
+ const auto *metaObject = PySide::retrieveMetaObject(self);
+ // The convention for QtRO is if a property is named "something" and uses
+ // push, the name of the push method will be "pushSomething". But it is
+ // possible the name would be "Something", so we need to check upper
+ // and lower case.
+ auto name = callData->name.sliced(4);
+ auto index = metaObject->indexOfProperty(name.constData());
+ if (index < 0) {
+ name[0] = tolower(name[0]); // Try lower case
+ index = metaObject->indexOfProperty(name.constData());
+ }
+ // It is possible a .rep names a Slot "push" or "pushSomething" that
+ // isn't generated for a property. Let that fall through to regular
+ // method handling.
+ if (index >= 0) {
+ // Call the custom descriptor's set method
+ auto result = PyObject_SetAttrString(self, name.constData(),
+ PyTuple_GetItem(args, 0));
+ if (result < 0) {
+ PyErr_Print();
+ return nullptr;
+ }
+ Py_RETURN_NONE;
+ }
+ }
+ // TODO: This doesn't do much, as it is "eaten" by a PyError_Print in
+ // SignalManager::handleMetaCallError()
+ // Is there a better way to address slots that need to be implemented?
+ PyErr_Format(PyExc_NotImplementedError, "** The method %s is not implemented",
+ callData->name.constData());
+ return nullptr;
+ }
+
+ PyErr_SetString(PyExc_RuntimeError, "Unknown capsule type");
+ return nullptr;
+ }
+};
+
+struct ReplicaDefs
+{
+ static PyTypeObject *getSbkType()
+ {
+ static PyTypeObject *sbkType =
+ Shiboken::Conversions::getPythonTypeObject("QRemoteObjectReplica");
+ return sbkType;
+ }
+
+ static PyObject *getBases()
+ {
+ static PyObject *bases = PyTuple_Pack(1, getSbkType());
+ return bases;
+ }
+
+ static const char *getTypePrefix()
+ {
+ return "2:PySide6.QtRemoteObjects.DynamicReplica.";
+ }
+
+ static int tp_init(PyObject *self, PyObject *args, PyObject *kwds)
+ {
+ static initproc initFunc = reinterpret_cast<initproc>(PepType_GetSlot(getSbkType(),
+ Py_tp_init));
+ QRemoteObjectReplica *replica = nullptr;
+ if (PyTuple_Size(args) == 0) {
+ if (initFunc(self, args, kwds) < 0)
+ return -1;
+ Shiboken::Conversions::pythonToCppPointer(getSbkType(), self, &replica);
+ } else { // Process replica with arguments passed from the added node.acquire method
+ PyObject *node = nullptr;
+ PyObject *constructorType = nullptr;
+ PyObject *name = nullptr;
+ static PyTypeObject *nodeType = Shiboken::Conversions::getPythonTypeObject("QRemoteObjectNode");
+ if (!PyArg_UnpackTuple(args, "Replica.__init__", 2, 3, &node, &constructorType, &name) ||
+ !PySide::inherits(Py_TYPE(node), nodeType->tp_name)) {
+ PyErr_SetString(PyExc_TypeError,
+ "Replicas can be initialized with no arguments or by node.acquire only");
+ return -1;
+ }
+ static auto *constructorArgs = PyTuple_Pack(1, constructorType);
+ if (initFunc(self, constructorArgs, kwds) < 0)
+ return -1;
+ if (name)
+ PyObject_CallMethod(self, "initializeNode", "OO", node, name);
+ else
+ PyObject_CallMethod(self, "initializeNode", "O", node);
+ Shiboken::Conversions::pythonToCppPointer(getSbkType(), self, &replica);
+ }
+ if (!replica) {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to initialize replica");
+ return -1;
+ }
+ // Get the properties from the type
+ PyTypeObject *type = Py_TYPE(self);
+ auto *pyProperties = PyObject_GetAttr(reinterpret_cast<PyObject *>(type), propertiesAttr());
+ if (!pyProperties) {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to get properties from type");
+ return -1;
+ }
+ // Make a copy of the properties and set them on the replica
+ auto *propPtr = reinterpret_cast<QVariantList *>(PyCapsule_GetPointer(pyProperties, nullptr));
+ auto propertiesCopy = QVariantList(*propPtr);
+ static_cast<FriendlyReplica *>(replica)->setProperties(std::move(propertiesCopy));
+ return 0;
+ }
+
+ static PyObject *capsule_method_handler(PyObject *payload, PyObject *args)
+ {
+ auto *methodData = reinterpret_cast<CapsuleDescriptorData *>(PyCapsule_GetPointer(payload,
+ "Payload"));
+ if (!methodData) {
+ PyErr_SetString(PyExc_RuntimeError, "Invalid call to dynamic method. Missing payload.");
+ return nullptr;
+ }
+ PyObject *self = methodData->self;
+ QRemoteObjectReplica *replica = nullptr;
+ Shiboken::Conversions::pythonToCppPointer(getSbkType(), self, &replica);
+ if (PyCapsule_IsValid(methodData->payload, "PropertyCapsule")) {
+ auto *capsule = PyCapsule_GetPointer(methodData->payload, "PropertyCapsule");
+ if (capsule) {
+ auto *callData = reinterpret_cast<PropertyCapsule *>(capsule);
+ QVariant currentVariant = static_cast<FriendlyReplica *>(replica)->propAsVariant(callData->indexInObject);
+
+ // Handle getter
+ if (PyTuple_Size(args) == 0) // Getter
+ return toPython(currentVariant);
+
+ // Handle setter - currentVariant is a copy, so we can modify it
+ if (PyTuple_Size(args) != 1) {
+ PyErr_SetString(PyExc_TypeError,
+ "Property setter takes exactly one argument");
+ return nullptr;
+ }
+ Conversions::SpecificConverter converter(currentVariant.metaType().name());
+ auto metaType = currentVariant.metaType();
+ if (metaType.flags().testFlag(QMetaType::IsEnumeration)) {
+ converter.toCpp(PyTuple_GetItem(args, 0), currentVariant.data());
+ currentVariant.convert(metaType);
+ } else {
+ converter.toCpp(PyTuple_GetItem(args, 0), currentVariant.data());
+ }
+ if (PyErr_Occurred()) // POD conversion can produce an error
+ return nullptr;
+ QVariantList _args{currentVariant};
+ static_cast<FriendlyReplica *>(replica)->send(QMetaObject::WriteProperty, callData->propertyIndex, _args);
+ Py_RETURN_NONE;
+ }
+ }
+ if (PyCapsule_IsValid(methodData->payload, "MethodCapsule")) {
+ auto *capsule = PyCapsule_GetPointer(methodData->payload, "MethodCapsule");
+ if (capsule) {
+ auto *callData = reinterpret_cast<MethodCapsule *>(capsule);
+ if (PyTuple_Size(args) != callData->argumentTypes.size()) {
+ PyErr_SetString(PyExc_TypeError,
+ "Method called with incorrect number of arguments");
+ return nullptr;
+ }
+ QVariantList _args;
+ static Conversions::SpecificConverter argsConverter("QVariantList");
+ argsConverter.toCpp(args, &_args);
+ if (PyErr_Occurred()) // POD conversion can produce an error
+ return nullptr;
+ if (!callData->returnType.isValid() ||
+ (callData->returnType.isValid() && callData->returnType.id() == QMetaType::Void)) {
+ static_cast<FriendlyReplica *>(replica)->send(QMetaObject::InvokeMetaMethod, callData->methodIndex, _args);
+ Py_RETURN_NONE;
+ }
+ QRemoteObjectPendingCall *cppResult = new QRemoteObjectPendingCall;
+ *cppResult = static_cast<FriendlyReplica *>(replica)->sendWithReply(QMetaObject::InvokeMetaMethod,
+ callData->methodIndex, _args);
+ static PyTypeObject *baseType =
+ Shiboken::Conversions::getPythonTypeObject("QRemoteObjectPendingCall");
+ Q_ASSERT(baseType);
+ auto *pyResult = Shiboken::Object::newObject(baseType, cppResult, true, true);
+ return pyResult;
+ }
+ }
+
+ PyErr_SetString(PyExc_RuntimeError, "Unknown capsule type");
+ return nullptr;
+ }
+};
+
+static int DynamicType_traverse(PyObject *self, visitproc visit, void *arg)
+{
+ auto traverseProc = reinterpret_cast<traverseproc>(PepType_GetSlot(SbkObject_TypeF(),
+ Py_tp_traverse));
+ return traverseProc(self, visit, arg);
+}
+
+static int DynamicType_clear(PyObject *self)
+{
+ auto clearProc = reinterpret_cast<inquiry>(PepType_GetSlot(SbkObject_TypeF(), Py_tp_clear));
+ return clearProc(self);
+}
+
+static PyMethodDef DynamicClass_methods[] = {
+ {"get_enum", reinterpret_cast<PyCFunction>(DynamicType_get_enum), METH_O | METH_CLASS,
+ "Get enum type by name"},
+ {nullptr, nullptr, 0, nullptr}
+};
+
+static PyType_Slot DynamicClass_slots[] = {
+ {Py_tp_base, nullptr}, // inserted by introduceWrapperType
+ {Py_tp_init, nullptr}, // inserted by createDynamicType
+ {Py_tp_traverse, reinterpret_cast<void *>(DynamicType_traverse)},
+ {Py_tp_clear, reinterpret_cast<void *>(DynamicType_clear)},
+ {Py_tp_methods, reinterpret_cast<void *>(DynamicClass_methods)},
+ {0, nullptr}
+};
+
+} // extern "C"
+
+template <typename T, typename BaseType>
+PyTypeObject *createDynamicClassImpl(QMetaObject *meta)
+{
+ DynamicClass_slots[1].pfunc = reinterpret_cast<void*>(T::tp_init);
+
+ auto fullTypeName = QByteArray{T::getTypePrefix()} + meta->className();
+ PyType_Spec spec = {
+ fullTypeName.constData(),
+ 0,
+ 0,
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
+ DynamicClass_slots
+ };
+
+ auto type = Shiboken::ObjectType::introduceWrapperType(
+ reinterpret_cast<PyObject *>(PySideRepFile_TypeF()),
+ meta->className(),
+ meta->className(),
+ &spec,
+ &Shiboken::callCppDestructor<BaseType>,
+ T::getBases(),
+ Shiboken::ObjectType::WrapperFlags::InternalWrapper);
+
+ auto *self = reinterpret_cast<PyObject *>(type);
+ if (create_managed_py_enums(self, meta) < 0)
+ return nullptr;
+
+ PySide::Signal::registerSignals(type, meta);
+ Shiboken::ObjectType::setSubTypeInitHook(type, &PySide::initQObjectSubType);
+ PySide::initDynamicMetaObject(type, meta, 0); // Size 0?
+
+ PyMethodDef method = {
+ nullptr,
+ reinterpret_cast<PyCFunction>(T::capsule_method_handler),
+ METH_VARARGS,
+ nullptr
+ };
+ for (int i = meta->propertyOffset(); i < meta->propertyCount(); ++i) {
+ // Create a PropertyCapsule for each property to store the info needed for
+ // the handler. Assign the __get__ and (if needed) __set__ attributes to a
+ // PySideProperty which becomes the attribute set on the new type.
+ auto metaProperty = meta->property(i);
+ PyObject *kwds = PyDict_New();
+ auto metaType = metaProperty.metaType();
+ auto *pyPropertyType = PyUnicode_FromString(metaType.name());
+ PyDict_SetItemString(kwds, "type", pyPropertyType);
+ Py_DECREF(pyPropertyType);
+
+ method.ml_name = metaProperty.name();
+ auto *pc = new PropertyCapsule{metaProperty.name(), i, i - meta->propertyOffset()};
+ auto capsule = PyCapsule_New(pc, "PropertyCapsule", [](PyObject *capsule) {
+ delete static_cast<PropertyCapsule *>(PyCapsule_GetPointer(capsule, "PropertyCapsule"));
+ });
+ auto capsulePropObject = make_capsule_property(&method, capsule,
+ metaProperty.isWritable());
+ PyObject *fget = PyObject_GetAttrString(capsulePropObject, "__get__");
+ PyDict_SetItemString(kwds, "fget", fget);
+ if (metaProperty.isWritable()) {
+ PyObject *fset = PyObject_GetAttrString(capsulePropObject, "__set__");
+ PyDict_SetItemString(kwds, "fset", fset);
+ if (metaProperty.hasNotifySignal()) {
+ auto nameString = metaProperty.notifySignal().name();
+ auto *notify = PyObject_GetAttrString(reinterpret_cast<PyObject *>(type),
+ nameString.constData());
+ PyDict_SetItemString(kwds, "notify", notify);
+ }
+ }
+ PyObject *pyProperty = PyObject_Call(reinterpret_cast<PyObject *>(PySideProperty_TypeF()),
+ PyTuple_New(0), kwds);
+ if (PyObject_SetAttrString(reinterpret_cast<PyObject *>(type),
+ metaProperty.name(), pyProperty) < 0) {
+ return nullptr;
+ }
+ Py_DECREF(pyProperty);
+ }
+ for (int i = meta->methodOffset(); i < meta->methodCount(); ++i) {
+ // Create a CapsuleMethod for each Slot method to store the info needed
+ // for the handler.
+ auto metaMethod = meta->method(i);
+ // Note: We are creating our custom metatype ourselves, which makes our added
+ // (non-signal), methods return QMetaMethod::MethodType::Method, not
+ // MethodType::Slot. This is fine, we just need to create a CapsuleMethod
+ // for those methods.
+ if (metaMethod.methodType() == QMetaMethod::MethodType::Signal)
+ continue;
+ auto name = metaMethod.name();
+ method.ml_name = name.constData();
+ QList<QMetaType> argumentTypes;
+ for (int j = 0; j < metaMethod.parameterCount(); ++j)
+ argumentTypes << metaMethod.parameterMetaType(j);
+ MethodCapsule *capsuleData = new MethodCapsule{metaMethod.name(),
+ metaMethod.methodIndex(),
+ std::move(argumentTypes),
+ metaMethod.returnMetaType()};
+ add_capsule_method_to_type(type, &method,
+ PyCapsule_New(capsuleData, "MethodCapsule",
+ [](PyObject *capsule) {
+ delete reinterpret_cast<MethodCapsule *>(PyCapsule_GetPointer(capsule, "MethodCapsule"));
+ }));
+ }
+
+ return type;
+}
+
+PyTypeObject *createDynamicClass(QMetaObject *meta, PyObject *properties_capsule)
+{
+ bool isSource;
+ if (strncmp(meta->superClass()->className(), "QObject", 7) == 0) {
+ isSource = true;
+ } else if (strncmp(meta->superClass()->className(), "QRemoteObjectReplica", 20) == 0) {
+ isSource = false;
+ } else {
+ PyErr_SetString(PyExc_RuntimeError,
+ "Dynamic type must be a subclass of QObject or QRemoteObjectReplica");
+ return nullptr;
+ }
+
+ PyTypeObject *newType = nullptr;
+
+ if (isSource)
+ newType = createDynamicClassImpl<SourceDefs, QObject>(meta);
+ else
+ newType = createDynamicClassImpl<ReplicaDefs, QRemoteObjectReplica>(meta);
+
+ // Add the properties to the new type as an attribute
+ if (PyObject_SetAttr(reinterpret_cast<PyObject *>(newType), propertiesAttr(),
+ properties_capsule) < 0) {
+ Py_DECREF(newType);
+ return nullptr;
+ }
+
+ return newType;
+}
--- /dev/null
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef PYSIDE_DYNAMIC_CLASS_P_H
+#define PYSIDE_DYNAMIC_CLASS_P_H
+
+#include <sbkpython.h>
+
+#include <QtCore/qtclasshelpermacros.h>
+
+QT_FORWARD_DECLARE_STRUCT(QMetaObject)
+
+PyTypeObject *createDynamicClass(QMetaObject *meta, PyObject *properties_capsule);
+
+#endif // PYSIDE_DYNAMIC_CLASS_P_H
--- /dev/null
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "pysidedynamiccommon_p.h"
+#include "pysidedynamicenum_p.h"
+
+#include <sbkstring.h>
+
+#include <QtCore/qmetaobject.h>
+
+int capsule_count = 0;
+
+using namespace Shiboken;
+
+PyObject *toPython(const QVariant &variant)
+{
+ auto metaType = variant.metaType();
+ Conversions::SpecificConverter converter(metaType.name());
+ auto *value = converter.toPython(variant.data());
+ if (metaType.flags().testFlag(QMetaType::IsGadget)) {
+ // A single converter is used for all POD types - it converts to a Python
+ // tuple. We need an additional step to convert to our Python type for the POD.
+ // Thankfully, the converter stores the specific type we created, so we can call
+ // the constructor with the tuple.
+ auto *podType = Conversions::getPythonTypeObject(converter);
+ if (!podType) {
+ Py_DECREF(value);
+ PyErr_SetString(PyExc_RuntimeError, "Failed to get Python type for POD");
+ return nullptr;
+ }
+ PyObject *podValue = PyObject_CallObject(reinterpret_cast<PyObject *>(podType), value);
+ Py_DECREF(value);
+ if (!podValue) {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to create POD instance");
+ return nullptr;
+ }
+ return podValue;
+ }
+ if (metaType.flags().testFlag(QMetaType::IsEnumeration)) {
+ // Enums are converted to Python ints
+ auto *enumType = Conversions::getPythonTypeObject(converter);
+ if (!enumType) {
+ Py_DECREF(value);
+ PyErr_SetString(PyExc_RuntimeError, "Failed to get Python type for enum");
+ return nullptr;
+ }
+ PyObject *enumValue = PyObject_CallFunctionObjArgs(reinterpret_cast<PyObject *>(enumType),
+ value, nullptr);
+ Py_DECREF(value);
+ if (!enumValue) {
+ PyErr_Print();
+ PyErr_SetString(PyExc_RuntimeError, "Failed to create enum instance");
+ return nullptr;
+ }
+ return enumValue;
+ }
+ return value;
+}
+
+
+/**
+ * @brief Creates and manages memory for Python enum types for each QEnum in the
+ * provided QMetaObject.
+ *
+ * This function iterates over the enumerators in the provided QMetaObject,
+ * creates corresponding Python enum types, and stores them in a dictionary.
+ * The dictionary is then set as an attribute ()"_enum_data") on the provided
+ * Python object, to be accessed by the _get_enum that has been added to each
+ * of our dynamic types.
+ *
+ * These are "managed" in the sense that the enums clean up their converters
+ * using our PyCapsule method, and by adding the dictionary as a Python attribute,
+ * the dictionary will be cleaned up when the containing type is garbage
+ * collected.
+ *
+ * @param self A pointer to the Python object where the enum data will be stored.
+ * @param meta A pointer to the QMetaObject containing the enumerators.
+ * @return Returns 0 on success, or -1 on failure.
+ */
+int create_managed_py_enums(PyObject *self, QMetaObject *meta)
+{
+ PyObject *enum_data = PyDict_New();
+ for (int i = meta->enumeratorOffset(); i < meta->enumeratorCount(); ++i) {
+ auto metaEnum = meta->enumerator(i);
+ auto *enumType = createEnumType(&metaEnum);
+ if (!enumType) {
+ PyErr_Print();
+ PyErr_Format(PyExc_RuntimeError, "Failed to create enum type for POD '%s'",
+ meta->className());
+ return -1;
+ }
+ PyDict_SetItemString(enum_data, metaEnum.enumName(),
+ reinterpret_cast<PyObject *>(enumType));
+ Py_DECREF(enumType);
+ }
+ if (PyObject_SetAttrString(self, "_enum_data", enum_data) < 0) {
+ PyErr_Print();
+ qWarning() << "Failed to set _enum_data attribute on type"
+ << reinterpret_cast<PyTypeObject *>(self)->tp_name;
+ return -1;
+ }
+ Py_DECREF(enum_data);
+
+ return 0;
+}
+
+PyObject *DynamicType_get_enum(PyObject *self, PyObject *name)
+{
+ // Our enum types are always stored in a dictionary attribute named "_enum_data"
+ PyObject *enum_dict = PyObject_GetAttrString(self, "_enum_data");
+ if (!enum_dict) {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to get _enum_data attribute");
+ return nullptr;
+ }
+
+ PyObject *enum_type = PyDict_GetItem(enum_dict, name);
+ Py_DECREF(enum_dict);
+
+ if (!enum_type) {
+ PyErr_Format(PyExc_KeyError, "Enum '%s' not found", String::toCString(name));
+ return nullptr;
+ }
+
+ Py_INCREF(enum_type);
+ return enum_type;
+}
--- /dev/null
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef PYSIDE_DYNAMIC_COMMON_P_H
+#define PYSIDE_DYNAMIC_COMMON_P_H
+
+#include <sbkconverter.h>
+
+#include <QtCore/qlist.h>
+#include <QtCore/qvariant.h>
+#include <QtCore/qmetatype.h>
+
+PyObject *toPython(const QVariant &variant);
+int create_managed_py_enums(PyObject *self, QMetaObject *meta);
+PyObject *DynamicType_get_enum(PyObject *self, PyObject *name);
+
+// Data for dynamically created property handlers
+struct PropertyCapsule
+{
+ QByteArray name;
+ int propertyIndex; // meta->indexOfProperty() - including offset
+ int indexInObject; // Index minus offset for indexing into QVariantList
+};
+
+// Data for dynamically created method handlers
+struct MethodCapsule
+{
+ QByteArray name;
+ int methodIndex;
+ QList<QMetaType> argumentTypes;
+ QMetaType returnType; // meta->indexOfMethod() - including offset
+};
+
+// These functions are used to create a PyCapsule holding a pointer to a C++
+// object, which is set as an attribute on a Python type. When the Python
+// type is garbage collected, the type's attributes are as well, resulting in
+// the capsule's cleanup running to delete the pointer. This won't be as
+// efficient as a custom tp_free on the type, but it's easier to manage.
+// And it only runs when as all references to the type (and all instances) are
+// released, so it won't be used frequently.
+
+extern int capsule_count;
+
+template <typename T>
+void Capsule_destructor(PyObject *capsule)
+{
+ capsule_count--;
+ T pointer = static_cast<T>(PyCapsule_GetPointer(capsule, nullptr));
+ delete pointer;
+ pointer = nullptr;
+}
+
+template <>
+inline void Capsule_destructor<SbkConverter *>(PyObject *capsule)
+{
+ capsule_count--;
+ SbkConverter *pointer = static_cast<SbkConverter *>(PyCapsule_GetPointer(capsule, nullptr));
+ Shiboken::Conversions::deleteConverter(pointer);
+ pointer = nullptr;
+}
+
+template <typename T>
+int set_cleanup_capsule_attr_for_pointer(PyTypeObject *type, const char *name, T pointer)
+{
+ static_assert(std::is_pointer<T>::value, "T must be a pointer type");
+
+ if (!pointer) {
+ PyErr_SetString(PyExc_RuntimeError, "Pointer is null");
+ return -1;
+ }
+ auto capsule = PyCapsule_New(pointer, nullptr, Capsule_destructor<T>);
+ if (!capsule)
+ return -1; // Propagate the error
+
+ if (PyObject_SetAttrString(reinterpret_cast<PyObject *>(type), name, capsule) < 0)
+ return -1; // Propagate the error
+
+ Py_DECREF(capsule);
+ capsule_count++;
+
+ return 0;
+}
+
+#endif // PYSIDE_DYNAMIC_COMMON_P_H
--- /dev/null
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "pysidedynamicenum_p.h"
+#include "pysidedynamiccommon_p.h"
+
+#include <autodecref.h>
+#include <sbkconverter.h>
+#include <sbkenum.h>
+
+#include <QtCore/qmetaobject.h>
+
+using namespace Shiboken;
+
+// Remote Objects transfer enums as integers of the underlying type.
+#define CREATE_ENUM_CONVERSION_FUNCTIONS(SUFFIX, INT_TYPE, PY_TYPE) \
+static void pythonToCpp_PyEnum_QEnum_##SUFFIX(PyObject *pyIn, void *cppOut) \
+{ \
+ Enum::EnumValueType value = Enum::getValue(pyIn); \
+ INT_TYPE val(value); \
+ *reinterpret_cast<INT_TYPE *>(cppOut) = val; \
+} \
+static PythonToCppFunc is_PyEnum_PythonToCpp_QEnum_##SUFFIX##_Convertible(PyObject *pyIn) \
+{ \
+ if (Enum::check(pyIn)) \
+ return pythonToCpp_PyEnum_QEnum_##SUFFIX; \
+ return {}; \
+} \
+static PyObject *cppToPython_QEnum_##SUFFIX##_PyEnum(const void *cppIn) \
+{ \
+ auto convertedCppIn = *reinterpret_cast<const INT_TYPE *>(cppIn); \
+ return PY_TYPE(convertedCppIn); \
+}
+
+CREATE_ENUM_CONVERSION_FUNCTIONS(I8, int8_t, PyLong_FromLong)
+CREATE_ENUM_CONVERSION_FUNCTIONS(I16, int16_t, PyLong_FromLong)
+CREATE_ENUM_CONVERSION_FUNCTIONS(I32, int32_t, PyLong_FromLong)
+CREATE_ENUM_CONVERSION_FUNCTIONS(U8, uint8_t, PyLong_FromUnsignedLong)
+CREATE_ENUM_CONVERSION_FUNCTIONS(U16, uint16_t, PyLong_FromUnsignedLong)
+CREATE_ENUM_CONVERSION_FUNCTIONS(U32, uint32_t, PyLong_FromUnsignedLong)
+CREATE_ENUM_CONVERSION_FUNCTIONS(I64, int64_t, PyLong_FromLongLong)
+CREATE_ENUM_CONVERSION_FUNCTIONS(U64, uint64_t, PyLong_FromUnsignedLongLong)
+
+PyTypeObject *createEnumType(QMetaEnum *metaEnum)
+{
+ static const auto namePrefix = QByteArrayLiteral("2:PySide6.QtRemoteObjects.DynamicEnum.");
+ auto fullName = namePrefix + metaEnum->scope() + "." + metaEnum->enumName();
+
+ AutoDecRef args(PyList_New(0));
+ auto *pyEnumItems = args.object();
+ auto metaType = metaEnum->metaType();
+ auto underlyingType = metaType.underlyingType();
+ bool isUnsigned = underlyingType.flags().testFlag(QMetaType::IsUnsignedEnumeration);
+ for (int idx = 0; idx < metaEnum->keyCount(); ++idx) {
+ auto *key = PyUnicode_FromString(metaEnum->key(idx));
+ auto *key_value = PyTuple_New(2);
+ PyTuple_SetItem(key_value, 0, key);
+ // Value should only return a nullopt if there is no metaObject or the index is not valid
+ auto valueOpt = metaEnum->value64(idx);
+ if (!valueOpt) {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to get value64 from enum");
+ return nullptr;
+ }
+ if (isUnsigned) {
+ auto *value = PyLong_FromUnsignedLongLong(*valueOpt);
+ PyTuple_SetItem(key_value, 1, value);
+ } else {
+ auto *value = PyLong_FromLongLong(*valueOpt);
+ PyTuple_SetItem(key_value, 1, value);
+ }
+ PyList_Append(pyEnumItems, key_value);
+ }
+
+ PyTypeObject *newType{};
+ if (metaEnum->isFlag())
+ newType = Enum::createPythonEnum(fullName.constData(), pyEnumItems, "Flag");
+ else
+ newType = Enum::createPythonEnum(fullName.constData(), pyEnumItems);
+
+ SbkConverter *converter = nullptr;
+ switch (underlyingType.sizeOf()) {
+ case 1:
+ if (isUnsigned) {
+ converter = Conversions::createConverter(newType,
+ cppToPython_QEnum_U8_PyEnum);
+ Conversions::addPythonToCppValueConversion(converter,
+ pythonToCpp_PyEnum_QEnum_U8,
+ is_PyEnum_PythonToCpp_QEnum_U8_Convertible);
+ } else {
+ converter = Conversions::createConverter(newType,
+ cppToPython_QEnum_I8_PyEnum);
+ Conversions::addPythonToCppValueConversion(converter,
+ pythonToCpp_PyEnum_QEnum_I8,
+ is_PyEnum_PythonToCpp_QEnum_I8_Convertible);
+ }
+ break;
+ case 2:
+ if (isUnsigned) {
+ converter = Conversions::createConverter(newType,
+ cppToPython_QEnum_U16_PyEnum);
+ Conversions::addPythonToCppValueConversion(converter,
+ pythonToCpp_PyEnum_QEnum_U16,
+ is_PyEnum_PythonToCpp_QEnum_U16_Convertible);
+ } else {
+ converter = Conversions::createConverter(newType,
+ cppToPython_QEnum_I16_PyEnum);
+ Conversions::addPythonToCppValueConversion(converter,
+ pythonToCpp_PyEnum_QEnum_I16,
+ is_PyEnum_PythonToCpp_QEnum_I16_Convertible);
+ }
+ break;
+ case 4:
+ if (isUnsigned) {
+ converter = Conversions::createConverter(newType,
+ cppToPython_QEnum_U32_PyEnum);
+ Conversions::addPythonToCppValueConversion(converter,
+ pythonToCpp_PyEnum_QEnum_U32,
+ is_PyEnum_PythonToCpp_QEnum_U32_Convertible);
+ } else {
+ converter = Conversions::createConverter(newType,
+ cppToPython_QEnum_I32_PyEnum);
+ Conversions::addPythonToCppValueConversion(converter,
+ pythonToCpp_PyEnum_QEnum_I32,
+ is_PyEnum_PythonToCpp_QEnum_I32_Convertible);
+ }
+ break;
+ case 8:
+ if (isUnsigned) {
+ converter = Conversions::createConverter(newType,
+ cppToPython_QEnum_U64_PyEnum);
+ Conversions::addPythonToCppValueConversion(converter,
+ pythonToCpp_PyEnum_QEnum_U64,
+ is_PyEnum_PythonToCpp_QEnum_U64_Convertible);
+ } else {
+ converter = Conversions::createConverter(newType,
+ cppToPython_QEnum_I64_PyEnum);
+ Conversions::addPythonToCppValueConversion(converter,
+ pythonToCpp_PyEnum_QEnum_I64,
+ is_PyEnum_PythonToCpp_QEnum_I64_Convertible);
+ }
+ break;
+ default:
+ PyErr_SetString(PyExc_RuntimeError, "Unsupported enum underlying type");
+ return nullptr;
+ }
+ auto scopedName = QByteArray(metaEnum->scope()) + "::" + metaEnum->enumName();
+ Conversions::registerConverterName(converter, scopedName.constData());
+ Conversions::registerConverterName(converter, metaEnum->enumName());
+ // createConverter increases the ref count of type, but that will create a
+ // circular reference when we add the capsule with the converter's pointer
+ // to the type's attributes. So we need to decrease the ref count on the
+ // type after calling createConverter.
+ Py_DECREF(newType);
+ if (set_cleanup_capsule_attr_for_pointer(newType, "_converter_capsule", converter) < 0)
+ return nullptr;
+
+ return newType;
+}
--- /dev/null
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef PYSIDE_DYNAMIC_ENUM_P_H
+#define PYSIDE_DYNAMIC_ENUM_P_H
+
+#include <sbkpython.h>
+
+#include <QtCore/qtclasshelpermacros.h>
+
+QT_FORWARD_DECLARE_CLASS(QMetaEnum)
+
+PyTypeObject *createEnumType(QMetaEnum *metaEnum);
+
+#endif // PYSIDE_DYNAMIC_ENUM_P_H
--- /dev/null
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "pysidedynamicpod_p.h"
+#include "pysidecapsulemethod_p.h"
+#include "pysidedynamiccommon_p.h"
+
+#include <autodecref.h>
+#include <helper.h>
+#include <pep384ext.h>
+#include <sbkconverter.h>
+#include <sbkstaticstrings.h>
+#include <sbkstring.h>
+
+#include <pysidestaticstrings.h>
+
+#include <QtCore/qmetaobject.h>
+
+using namespace Shiboken;
+
+extern "C"
+{
+
+struct PodDefs
+{
+ static PyObject *tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+ {
+ SBK_UNUSED(kwds);
+ AutoDecRef param_types(PyObject_GetAttrString(reinterpret_cast<PyObject *>(type),
+ "__param_types__"));
+ if (!param_types) {
+ PyErr_Format(PyExc_RuntimeError, "Failed to get POD attributes for type %s",
+ type->tp_name);
+ return nullptr;
+ }
+
+ // param_types is a tuple of PyTypeObject pointers
+ Py_ssize_t size = PyTuple_Size(param_types);
+ if (size != PyTuple_Size(args)) {
+ PyErr_Format(PyExc_TypeError,
+ "Incorrect number of arguments for type %s. Expected %zd.",
+ type->tp_name, size);
+ return nullptr;
+ }
+
+ PyObject *self = PepExt_Type_GetAllocSlot(type)(type, size);
+
+ if (!self)
+ return nullptr;
+
+ for (Py_ssize_t i = 0; i < size; ++i) {
+ PyObject *expected_type = PyTuple_GetItem(param_types, i);
+ PyObject *item = PyTuple_GetItem(args, i);
+ // Check if the item is an instance of the expected type
+ if (PyObject_IsInstance(item, expected_type)) {
+ Py_INCREF(item);
+ PyTuple_SetItem(self, i, item);
+ } else {
+ // Try to convert the item to the expected type
+ PyObject *converted_item = PyObject_CallFunctionObjArgs(expected_type, item, nullptr);
+ if (!converted_item) {
+ Py_DECREF(self);
+ PyErr_Format(PyExc_TypeError, "Argument %zd must be convertible to type %s", i,
+ reinterpret_cast<PyTypeObject *>(expected_type)->tp_name);
+ return nullptr;
+ }
+ PyTuple_SetItem(self, i, converted_item);
+ }
+ }
+
+ return self;
+ }
+
+ static PyObject *tp_repr(PyObject *self)
+ {
+ auto *type = Py_TYPE(self);
+ std::string repr(type->tp_name);
+ repr += "(";
+ for (Py_ssize_t i = 0; i < PyTuple_Size(self); ++i) {
+ if (i > 0)
+ repr += ", ";
+
+ PyObject *item_repr = PyObject_Repr(PyTuple_GetItem(self, i));
+ repr += String::toCString(item_repr);
+ }
+ repr += ")";
+ return PyUnicode_FromString(repr.c_str());
+ }
+
+ static PyObject *CapsuleMethod_handler(PyObject *payload, PyObject * /* args */)
+ {
+ auto *methodData = reinterpret_cast<CapsuleDescriptorData *>(
+ PyCapsule_GetPointer(payload, "Payload"));
+ if (!methodData) {
+ PyErr_SetString(PyExc_RuntimeError, "Invalid call to dynamic method. Missing payload.");
+ return nullptr;
+ }
+ PyObject *self = methodData->self;
+ if (PyCapsule_IsValid(methodData->payload, "PropertyCapsule")) {
+ // Handle property getter/setter against our hidden properties attribute
+ auto *capsule = PyCapsule_GetPointer(methodData->payload, "PropertyCapsule");
+ if (capsule) {
+ auto *callData = reinterpret_cast<PropertyCapsule *>(capsule);
+ if (callData->indexInObject < 0 || callData->indexInObject >= PyTuple_Size(self)) {
+ PyErr_Format(PyExc_RuntimeError, "Unknown property method: %s",
+ callData->name.constData());
+ return nullptr;
+ }
+ auto *val = PyTuple_GetItem(self, callData->indexInObject);
+ Py_INCREF(val);
+ return val;
+ }
+ }
+
+ PyErr_SetString(PyExc_RuntimeError, "Unknown capsule type");
+ return nullptr;
+ }
+};
+
+static PyMethodDef DynamicPod_tp_methods[] = {
+ {"get_enum", reinterpret_cast<PyCFunction>(DynamicType_get_enum), METH_O | METH_CLASS,
+ "Get enum type by name"},
+ {nullptr, nullptr, 0, nullptr}
+};
+
+static PyType_Slot DynamicPod_slots[] = {
+ {Py_tp_base, reinterpret_cast<void *>(&PyTuple_Type)},
+ {Py_tp_new, reinterpret_cast<void *>(PodDefs::tp_new)},
+ {Py_tp_repr, reinterpret_cast<void *>(PodDefs::tp_repr)},
+ {Py_tp_methods, reinterpret_cast<void *>(DynamicPod_tp_methods)},
+ {0, nullptr}
+};
+
+// C++ to Python conversion for POD types.
+static PyObject *cppToPython_POD_Tuple(const void *cppIn)
+{
+ const auto &cppInRef = *reinterpret_cast<const QVariantList *>(cppIn);
+ PyObject *pyOut = PyTuple_New(Py_ssize_t(cppInRef.size()));
+ Py_ssize_t idx = 0;
+ for (auto it = std::cbegin(cppInRef), end = std::cend(cppInRef); it != end; ++it, ++idx) {
+ static const Conversions::SpecificConverter argConverter("QVariant");
+ const auto &cppItem = *it;
+ PyTuple_SetItem(pyOut, idx, Shiboken::Conversions::copyToPython(argConverter, &cppItem));
+ }
+ return pyOut;
+}
+static void pythonToCpp_Tuple_POD(PyObject *pyIn, void *cppOut)
+{
+ auto &cppOutRef = *reinterpret_cast<QVariantList *>(cppOut);
+
+ Py_ssize_t tupleSize = PyTuple_Size(pyIn);
+ if (tupleSize != cppOutRef.size()) {
+ PyErr_Format(PyExc_ValueError,
+ "Size mismatch: tuple has %zd elements, but POD expects %d elements",
+ tupleSize, cppOutRef.size());
+ return;
+ }
+
+ for (Py_ssize_t i = 0; i < tupleSize; ++i) {
+ static const Conversions::SpecificConverter argConverter("QVariant");
+ PyObject *item = PyTuple_GetItem(pyIn, i);
+ QVariant &variant = cppOutRef[i];
+ Conversions::SpecificConverter converter(variant.metaType().name());
+ Shiboken::Conversions::pythonToCppCopy(converter, item, variant.data());
+ }
+}
+static PythonToCppFunc is_Tuple_PythonToCpp_POD_Convertible(PyObject *pyIn)
+{
+ if (PyTuple_Check(pyIn))
+ return pythonToCpp_Tuple_POD;
+
+ return {};
+}
+
+} // extern "C"
+
+PyTypeObject *createPodType(QMetaObject *meta)
+{
+ auto qualname = QByteArrayLiteral("DynamicPod.") + meta->className();
+ PyType_Spec spec = {
+ qualname.constData(),
+ 0,
+ 0,
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_TYPE_SUBCLASS,
+ DynamicPod_slots
+ };
+
+ PyObject *obType = PyType_FromSpec(&spec);
+ if (!obType)
+ return nullptr;
+
+ if (create_managed_py_enums(obType, meta) < 0)
+ return nullptr;
+
+ Py_ssize_t size = meta->propertyCount() - meta->propertyOffset();
+ AutoDecRef pyParamTypes(PyTuple_New(size));
+ for (int i = 0; i < size; ++i) {
+ auto metaProperty = meta->property(i + meta->propertyOffset());
+ auto metaType = metaProperty.metaType();
+ if (!metaType.isValid()) {
+ PyErr_Format(PyExc_RuntimeError, "Failed to get meta type for property %s",
+ metaProperty.name());
+ return nullptr;
+ }
+ auto *pyType = Conversions::getPythonTypeObject(metaType.name());
+ Py_INCREF(pyType);
+ PyTuple_SetItem(pyParamTypes, i, reinterpret_cast<PyObject *>(pyType));
+ }
+
+ auto *type = reinterpret_cast<PyTypeObject *>(obType);
+ PyMethodDef method = {
+ nullptr,
+ reinterpret_cast<PyCFunction>(PodDefs::CapsuleMethod_handler),
+ METH_VARARGS,
+ nullptr
+ };
+ for (int i = meta->propertyOffset(); i < meta->propertyCount(); ++i) {
+ // Create a PropertyCapsule for each property to store the info needed
+ // for the handler.
+ auto metaProperty = meta->property(i);
+
+ method.ml_name = metaProperty.name();
+ auto *capsule = PyCapsule_New(new PropertyCapsule{metaProperty.name(),
+ i,
+ i - meta->propertyOffset()},
+ "PropertyCapsule",
+ [](PyObject *capsule) {
+ delete static_cast<PropertyCapsule *>(
+ PyCapsule_GetPointer(capsule, "PropertyCapsule"));
+ });
+ auto *capsulePropObject = make_capsule_property(&method, capsule);
+ if (PyObject_SetAttrString(reinterpret_cast<PyObject *>(type), metaProperty.name(),
+ capsulePropObject) < 0) {
+ return nullptr;
+ }
+
+ Py_DECREF(capsulePropObject);
+ }
+
+ // createConverter increases the ref count of type, but that will create
+ // a circular reference. When we add the capsule with the converter's pointer
+ // to the type's attributes. So we need to decrease the ref count on the type
+ // after calling createConverter.
+ auto *converter = Shiboken::Conversions::createConverter(type, cppToPython_POD_Tuple);
+ Py_DECREF(type);
+ if (set_cleanup_capsule_attr_for_pointer(type, "_converter_capsule", converter) < 0)
+ return nullptr;
+ Shiboken::Conversions::registerConverterName(converter, meta->className());
+ Shiboken::Conversions::registerConverterName(converter, type->tp_name);
+ Shiboken::Conversions::addPythonToCppValueConversion(converter, pythonToCpp_Tuple_POD,
+ is_Tuple_PythonToCpp_POD_Convertible);
+
+ static PyObject *const module = String::createStaticString("PySide6.QtRemoteObjects");
+ AutoDecRef pyQualname(String::fromCString(qualname.constData()));
+ PyObject_SetAttr(obType, PyMagicName::qualname(), pyQualname);
+ PyObject_SetAttr(obType, PyMagicName::module(), module);
+ PyObject_SetAttrString(obType, "__param_types__", pyParamTypes);
+
+ return type;
+}
--- /dev/null
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef PYSIDE_DYNAMIC_POD_P_H
+#define PYSIDE_DYNAMIC_POD_P_H
+
+#include <sbkpython.h>
+
+#include <QtCore/qtclasshelpermacros.h>
+
+QT_FORWARD_DECLARE_STRUCT(QMetaObject)
+
+PyTypeObject *createPodType(QMetaObject *meta);
+
+#endif // PYSIDE_DYNAMIC_POD_P_H
--- /dev/null
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef PYSIDEREMOTEOBJECTS_H
+#define PYSIDEREMOTEOBJECTS_H
+
+#include <sbkpython.h>
+
+namespace PySide::RemoteObjects
+{
+
+void init(PyObject *module);
+
+} // namespace PySide::RemoteObjects
+
+#endif // PYSIDEREMOTEOBJECTS_H
--- /dev/null
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "pysiderephandler_p.h"
+#include "pysidedynamicclass_p.h"
+#include "pysidedynamicpod_p.h"
+#include "pysidedynamiccommon_p.h"
+
+#include <pep384ext.h>
+#include <sbkstring.h>
+#include <sbktypefactory.h>
+#include <signature.h>
+
+#include <pysideutils.h>
+
+#include <QtCore/qbuffer.h>
+#include <QtCore/qiodevice.h>
+#include <QtCore/qmetaobject.h>
+
+#include <QtRemoteObjects/qremoteobjectreplica.h>
+#include <QtRemoteObjects/qremoteobjectpendingcall.h>
+
+#include <private/qremoteobjectrepparser_p.h>
+
+using namespace Qt::StringLiterals;
+using namespace Shiboken;
+
+/**
+ * @file pysiderephandler.cpp
+ * @brief This file contains the implementation of the PySideRepFile type and its
+ * associated methods for handling Qt Remote Objects in PySide6.
+ *
+ * The PySideRepFile type provides functionality to parse and handle Qt Remote Objects
+ * (QtRO) files, and dynamically generate Python types for QtRO sources, replicas, and
+ * PODs (Plain Old Data structures).
+ *
+ * The RepFile_tp_methods array defines the methods available on the PySideRepFile object:
+ * - source: Generates a dynamic Python type for a QtRO source class.
+ * - replica: Generates a dynamic Python type for a QtRO replica class.
+ * - pod: Generates a dynamic Python type for a QtRO POD class.
+ *
+ * When generating a source or replica type, the generateDynamicType function is
+ * used, creating a new Python type based on the generated QMetaObject, and adds
+ * method descriptors for the required methods. A QVariantList for the types
+ * properties is also created, populated with default values if set in the input
+ * .rep file.
+*/
+
+static QVariantList generateProperties(QMetaObject *meta, const ASTClass &astClass);
+
+extern "C"
+{
+static PyObject *get_capsule_count()
+{
+ return PyLong_FromLong(capsule_count);
+}
+
+// Code for the PySideRepFile type
+static PyObject *RepFile_tp_string(PyObject *self);
+static PyObject *RepFile_tp_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds);
+static int RepFile_tp_init(PyObject *self, PyObject *args, PyObject *kwds);
+static void RepFile_tp_free(void *self);
+static void RepFile_tp_dealloc(PySideRepFile *self);
+
+static PyObject *RepFile_get_pods(PySideRepFile *self, void * /*unused*/);
+static PyObject *RepFile_get_replicas(PySideRepFile *self, void * /*unused*/);
+static PyObject *RepFile_get_sources(PySideRepFile *self, void * /*unused*/);
+
+bool instantiateFromDefaultValue(QVariant &variant, const QString &defaultValue);
+
+static PyObject *cppToPython_POD_Tuple(const void *cppIn);
+static void pythonToCpp_Tuple_POD(PyObject *pyIn, void *cppOut);
+static PythonToCppFunc is_Tuple_PythonToCpp_POD_Convertible(PyObject *pyIn);
+
+static PyGetSetDef RepFile_tp_getters[] = {
+ {"pod", reinterpret_cast<getter>(RepFile_get_pods), nullptr, "POD dictionary", nullptr},
+ {"replica", reinterpret_cast<getter>(RepFile_get_replicas), nullptr, "Replica dictionary", nullptr},
+ {"source", reinterpret_cast<getter>(RepFile_get_sources), nullptr, "Source dictionary", nullptr},
+ {nullptr, nullptr, nullptr, nullptr, nullptr} // Sentinel
+};
+
+static PyTypeObject *createRepFileType()
+{
+ PyType_Slot PySideRepFileType_slots[] = {
+ {Py_tp_str, reinterpret_cast<void *>(RepFile_tp_string)},
+ {Py_tp_init, reinterpret_cast<void *>(RepFile_tp_init)},
+ {Py_tp_new, reinterpret_cast<void *>(RepFile_tp_new)},
+ {Py_tp_free, reinterpret_cast<void *>(RepFile_tp_free)},
+ {Py_tp_dealloc, reinterpret_cast<void *>(RepFile_tp_dealloc)},
+ {Py_tp_getset, reinterpret_cast<void *>(RepFile_tp_getters)},
+ {0, nullptr}
+ };
+
+ PyType_Spec PySideRepFileType_spec = {
+ "2:PySide6.QtRemoteObjects.RepFile",
+ sizeof(PySideRepFile),
+ 0,
+ Py_TPFLAGS_DEFAULT,
+ PySideRepFileType_slots};
+ return SbkType_FromSpec(&PySideRepFileType_spec);
+}
+
+PyTypeObject *PySideRepFile_TypeF(void)
+{
+ static auto *type = createRepFileType();
+ return type;
+}
+
+static PyObject *RepFile_tp_string(PyObject *self)
+{
+ auto *cppSelf = reinterpret_cast<PySideRepFile *>(self);
+ QString result = QStringLiteral("RepFile(Classes: [%1], PODs: [%2])")
+ .arg(cppSelf->d->classes.join(", "_L1), cppSelf->d->pods.join(", "_L1));
+ return PyUnicode_FromString(result.toUtf8().constData());
+}
+
+static PyObject *RepFile_tp_new(PyTypeObject *subtype, PyObject * /* args */, PyObject * /* kwds */)
+{
+ auto *me = PepExt_TypeCallAlloc<PySideRepFile>(subtype, 0);
+ auto *priv = new PySideRepFilePrivate;
+ priv->podDict = PyDict_New();
+ if (!priv->podDict) {
+ delete priv;
+ return nullptr;
+ }
+ priv->replicaDict = PyDict_New();
+ if (!priv->replicaDict) {
+ Py_DECREF(priv->podDict);
+ delete priv;
+ return nullptr;
+ }
+ priv->sourceDict = PyDict_New();
+ if (!priv->sourceDict) {
+ Py_DECREF(priv->podDict);
+ Py_DECREF(priv->replicaDict);
+ delete priv;
+ return nullptr;
+ }
+ me->d = priv;
+ return reinterpret_cast<PyObject *>(me);
+}
+
+static PyObject *RepFile_get_pods(PySideRepFile *self, void * /* closure */)
+{
+ Py_INCREF(self->d->podDict);
+ return self->d->podDict;
+}
+
+static PyObject *RepFile_get_replicas(PySideRepFile *self, void * /* closure */)
+{
+ Py_INCREF(self->d->replicaDict);
+ return self->d->replicaDict;
+}
+
+static PyObject *RepFile_get_sources(PySideRepFile *self, void * /* closure */)
+{
+ Py_INCREF(self->d->sourceDict);
+ return self->d->sourceDict;
+}
+
+static void RepFile_tp_dealloc(PySideRepFile *self)
+{
+ Py_XDECREF(self->d->podDict);
+ Py_XDECREF(self->d->replicaDict);
+ Py_XDECREF(self->d->sourceDict);
+ PepExt_TypeCallFree(reinterpret_cast<PyObject *>(self));
+}
+
+static int parseArgsToAST(PyObject *args, PySideRepFile *repFile)
+{
+ // Verify args is a single string argument
+ if (PyTuple_Size(args) != 1 || !PyUnicode_Check(PyTuple_GetItem(args, 0))) {
+ PyErr_SetString(PyExc_TypeError, "RepFile constructor requires a single string argument");
+ return -1;
+ }
+
+ // Wrap contents into a QBuffer
+ const auto contents = PySide::pyStringToQString(PyTuple_GetItem(args, 0));
+ auto byteArray = contents.toUtf8();
+ QBuffer buffer(&byteArray);
+ buffer.open(QIODevice::ReadOnly);
+ RepParser repparser(buffer);
+ if (!repparser.parse()) {
+ PyErr_Format(PyExc_RuntimeError, "Error parsing input, line %d: error: %s",
+ repparser.lineNumber(), qPrintable(repparser.errorString()));
+ auto lines = contents.split("\n"_L1);
+ auto lMin = std::max(1, repparser.lineNumber() - 2);
+ auto lMax = std::min(repparser.lineNumber() + 2, int(lines.size()));
+ // Print a few lines around the error
+ qWarning() << "Contents:";
+ for (int i = lMin; i <= lMax; ++i) {
+ if (i == repparser.lineNumber())
+ qWarning().nospace() << " line " << i << ": > " << lines.at(i - 1);
+ else
+ qWarning().nospace() << " line " << i << ": " << lines.at(i - 1);
+ }
+ return -1;
+ }
+
+ repFile->d->ast = repparser.ast();
+
+ return 0;
+}
+
+static const char *repName(QMetaObject *meta)
+{
+ const int ind = meta->indexOfClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE);
+ return ind >= 0 ? meta->classInfo(ind).value() : "<Invalid RemoteObject>";
+}
+
+static int RepFile_tp_init(PyObject *self, PyObject *args, PyObject * /* kwds */)
+{
+ auto *cppSelf = reinterpret_cast<PySideRepFile *>(self);
+ if (parseArgsToAST(args, cppSelf) < 0)
+ return -1;
+
+ for (const auto &pod : std::as_const(cppSelf->d->ast.pods)) {
+ cppSelf->d->pods << pod.name;
+ auto *qobject = new QObject;
+ auto *meta = createAndRegisterMetaTypeFromPOD(pod, qobject);
+ if (!meta) {
+ delete qobject;
+ PyErr_Format(PyExc_RuntimeError, "Failed to create meta object for POD '%s'",
+ pod.name.toUtf8().constData());
+ return -1;
+ }
+
+ PyTypeObject *newType = createPodType(meta);
+ if (!newType) {
+ delete qobject;
+ PyErr_Print();
+ PyErr_Format(PyExc_RuntimeError, "Failed to create POD type for POD '%s'",
+ pod.name.toUtf8().constData());
+ return -1;
+ }
+ if (set_cleanup_capsule_attr_for_pointer(newType, "_qobject_capsule", qobject) < 0) {
+ delete qobject;
+ return -1;
+ }
+
+ PyDict_SetItemString(cppSelf->d->podDict, meta->className(),
+ reinterpret_cast<PyObject *>(newType));
+ Py_DECREF(newType);
+ }
+
+ if (PyErr_Occurred())
+ PyErr_Print();
+
+ for (const auto &cls : std::as_const(cppSelf->d->ast.classes)) {
+ cppSelf->d->classes << cls.name;
+
+ // Create Source type
+ {
+ auto *qobject = new QObject;
+ auto *meta = createAndRegisterSourceFromASTClass(cls, qobject);
+ if (!meta) {
+ delete qobject;
+ PyErr_Format(PyExc_RuntimeError, "Failed to create Source meta object for class '%s'",
+ cls.name.toUtf8().constData());
+ return -1;
+ }
+
+ auto properties = generateProperties(meta, cls);
+ // Check if an error occurred during generateProperties
+ if (PyErr_Occurred()) {
+ delete qobject;
+ return -1;
+ }
+ auto *propertiesPtr = new QVariantList(properties);
+ auto *pyCapsule = PyCapsule_New(propertiesPtr, nullptr, [](PyObject *capsule) {
+ delete reinterpret_cast<QVariantList *>(PyCapsule_GetPointer(capsule, nullptr));
+ });
+
+ PyTypeObject *newType = createDynamicClass(meta, pyCapsule);
+ if (!newType) {
+ delete qobject;
+ PyErr_Format(PyExc_RuntimeError,
+ "Failed to create Source Python type for class '%s'",
+ meta->className());
+ return -1;
+ }
+ if (set_cleanup_capsule_attr_for_pointer(newType, "_qobject_capsule", qobject) < 0) {
+ delete qobject;
+ return -1;
+ }
+
+ PyDict_SetItemString(cppSelf->d->sourceDict, repName(meta),
+ reinterpret_cast<PyObject *>(newType));
+ Py_DECREF(newType);
+ }
+
+ // Create Replica type
+ {
+ auto *qobject = new QObject;
+ auto *meta = createAndRegisterReplicaFromASTClass(cls, qobject);
+ if (!meta) {
+ delete qobject;
+ PyErr_Format(PyExc_RuntimeError,
+ "Failed to create Replica meta object for class '%s'",
+ qPrintable(cls.name));
+ return -1;
+ }
+
+ auto properties = generateProperties(meta, cls);
+ // Check if an error occurred during generateProperties
+ if (PyErr_Occurred()) {
+ delete qobject;
+ return -1;
+ }
+ auto *propertiesPtr = new QVariantList(properties);
+ auto *pyCapsule = PyCapsule_New(propertiesPtr, nullptr, [](PyObject *capsule) {
+ delete reinterpret_cast<QVariantList *>(PyCapsule_GetPointer(capsule, nullptr));
+ });
+
+ PyTypeObject *newType = createDynamicClass(meta, pyCapsule);
+ if (!newType) {
+ delete qobject;
+ PyErr_Format(PyExc_RuntimeError,
+ "Failed to create Replica Python type for class '%s'",
+ meta->className());
+ return -1;
+ }
+ if (set_cleanup_capsule_attr_for_pointer(newType, "_qobject_capsule", qobject) < 0) {
+ delete qobject;
+ return -1;
+ }
+
+ PyDict_SetItemString(cppSelf->d->replicaDict, repName(meta),
+ reinterpret_cast<PyObject *>(newType));
+ Py_DECREF(newType);
+ }
+ }
+
+ return 0;
+}
+
+static void RepFile_tp_free(void *self)
+{
+ PySideRepFile *obj = reinterpret_cast<PySideRepFile*>(self);
+ delete obj->d;
+}
+
+/**
+ * @brief Sets the QVariant value based on the provided default value text.
+ *
+ * This function attempts to set the provided QVariant's value based on the
+ * provided text. It evaluates the text as a Python expression, the the python
+ * type associated with the provided QMetaType. It first retrieves the Python
+ * type object corresponding to the given QMetaType, then constructs a Python
+ * expression to instantiate the type with the default value. The expression is
+ * evaluated using PyRun_String, and the result is then set on the QVariant.
+ * Note: The variant is passed by reference and modified in place.
+ *
+ * @return True if the instantiation is successful, false otherwise.
+ */
+bool instantiateFromDefaultValue(QVariant &variant, const QString &defaultValue)
+{
+ auto metaType = variant.metaType();
+ auto *pyType = Shiboken::Conversions::getPythonTypeObject(metaType.name());
+ if (!pyType) {
+ PyErr_Format(PyExc_TypeError, "Failed to find Python type for meta type: %s",
+ metaType.name());
+ return false;
+ }
+
+ // Evaluate the code
+ static PyObject *pyLocals = PyDict_New();
+
+ // Create the Python expression to evaluate
+ std::string code = std::string(pyType->tp_name) + '('
+ + defaultValue.toUtf8().constData() + ')';
+ PyObject *pyResult = PyRun_String(code.c_str(), Py_eval_input, pyLocals, pyLocals);
+
+ if (!pyResult) {
+ PyObject *ptype = nullptr;
+ PyObject *pvalue = nullptr;
+ PyObject *ptraceback = nullptr;
+ PyErr_Fetch(&ptype, &pvalue, &ptraceback);
+ PyErr_NormalizeException(&ptype, &pvalue, &ptraceback);
+ PyErr_Format(PyExc_TypeError,
+ "Failed to generate default value. Error: %s. Problematic code: %s",
+ Shiboken::String::toCString(PyObject_Str(pvalue)), code.c_str());
+ Py_XDECREF(ptype);
+ Py_XDECREF(pvalue);
+ Py_XDECREF(ptraceback);
+ Py_DECREF(pyLocals);
+ return false;
+ }
+
+ Conversions::SpecificConverter converter(metaType.name());
+ if (!converter) {
+ PyErr_Format(PyExc_TypeError, "Failed to find converter from Python type: %s to Qt type: %s",
+ pyResult->ob_type->tp_name, metaType.name());
+ Py_DECREF(pyResult);
+ return false;
+ }
+ converter.toCpp(pyResult, variant.data());
+ Py_DECREF(pyResult);
+
+ return true;
+}
+
+} // extern "C"
+
+static QVariantList generateProperties(QMetaObject *meta, const ASTClass &astClass)
+{
+ QVariantList properties;
+ auto propertyCount = astClass.properties.size();
+ properties.reserve(propertyCount);
+ for (auto i = 0; i < propertyCount; ++i) {
+ auto j = i + meta->propertyOffset(); // Corresponding property index in the meta object
+ auto metaProperty = meta->property(j);
+ auto metaType = metaProperty.metaType();
+ if (!metaType.isValid()) {
+ PyErr_Format(PyExc_RuntimeError, "Invalid meta type for property %d: %s", i,
+ astClass.properties[i].type.toUtf8().constData());
+ return {};
+ }
+ auto variant = QVariant(metaType);
+ if (auto defaultValue = astClass.properties[i].defaultValue; !defaultValue.isEmpty()) {
+ auto success = instantiateFromDefaultValue(variant, defaultValue);
+ if (!success) {
+ // Print a warning giving the property name, then propagate the error
+ qWarning() << "Failed to instantiate default value for property: "
+ << metaProperty.name();
+ return {};
+ }
+ }
+ properties << variant;
+ }
+ return properties;
+}
+
+namespace PySide::RemoteObjects
+{
+
+static const char *RepFile_SignatureStrings[] = {
+ "PySide6.RemoteObjects.RepFile(self,content:str)",
+ nullptr}; // Sentinel
+
+void init(PyObject *module)
+{
+ if (InitSignatureStrings(PySideRepFile_TypeF(), RepFile_SignatureStrings) < 0)
+ return;
+
+ qRegisterMetaType<QRemoteObjectPendingCall>();
+ qRegisterMetaType<QRemoteObjectPendingCallWatcher>();
+
+ Py_INCREF(PySideRepFile_TypeF());
+ PyModule_AddObject(module, "RepFile", reinterpret_cast<PyObject *>(PySideRepFile_TypeF()));
+
+ // Add a test helper to verify type reference counting
+ static PyMethodDef get_capsule_count_def = {
+ "getCapsuleCount", // name of the function in Python
+ reinterpret_cast<PyCFunction>(get_capsule_count), // C function pointer
+ METH_NOARGS, // flags indicating parameters
+ "Returns the current count of PyCapsule objects" // docstring
+ };
+
+ PyModule_AddObject(module, "getCapsuleCount", PyCFunction_New(&get_capsule_count_def, nullptr));
+}
+
+} // namespace PySide::RemoteObjects
--- /dev/null
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef PYSIDE_REPHANDLER_P_H
+#define PYSIDE_REPHANDLER_P_H
+
+#include <sbkpython.h>
+
+#include <QtRemoteObjects/repparser.h>
+
+#include <QtCore/qstringlist.h>
+
+struct PySideRepFilePrivate
+{
+ AST ast;
+ PyObject *podDict{};
+ PyObject *replicaDict{};
+ PyObject *sourceDict{};
+ QStringList classes;
+ QStringList pods;
+};
+
+extern "C"
+{
+ extern PyTypeObject *PySideRepFile_TypeF(void);
+
+ // Internal object
+ struct PySideRepFile
+ {
+ PyObject_HEAD
+ PySideRepFilePrivate *d;
+ };
+}; // extern "C"
+
+#endif // PYSIDE_REPHANDLER_P_H
target_compile_definitions(PySidePlugin PRIVATE -DQT_NO_KEYWORDS=1)
if(PYTHON_LIMITED_API)
- target_compile_definitions(PySidePlugin PRIVATE "-DPy_LIMITED_API=0x03080000")
+ target_compile_definitions(PySidePlugin PRIVATE "-DPy_LIMITED_API=0x03090000")
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
#include "designercustomwidgets.h"
-#include <QtCore/QCoreApplication>
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QFileInfoList>
-#include <QtCore/QLoggingCategory>
-#include <QtCore/QOperatingSystemVersion>
-#include <QtCore/QTextStream>
-#include <QtCore/QVariant>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qoperatingsystemversion.h>
+#include <QtCore/qtextstream.h>
+#include <QtCore/qvariant.h>
#include <string_view>
#include <utility>
#ifndef PY_DESIGNER_CUSTOM_WIDGETS_H_
#define PY_DESIGNER_CUSTOM_WIDGETS_H_
-#include <QtUiPlugin/QDesignerCustomWidgetCollectionInterface>
+#include <QtUiPlugin/customwidget.h>
// A Qt Designer plugin proxying the QDesignerCustomWidgetCollectionInterface
// instance set as as a dynamic property on QCoreApplication by the PySide6
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "customwidget.h"
+
#include <QtCore/qdebug.h>
+#include <autodecref.h>
+#include <basewrapper.h>
+#include <bindingmanager.h>
+#include <sbkconverter.h>
+
// Part of the static plugin linked to the QtUiLoader Python module,
// allowing it to create a custom widget written in Python.
PyCustomWidget::PyCustomWidget(PyObject *objectType) :
#ifndef PY_CUSTOM_WIDGET_H_
#define PY_CUSTOM_WIDGET_H_
-#include <shiboken.h>
+#include <sbkpython.h>
-#include <QtUiPlugin/QDesignerCustomWidgetInterface>
+#include <QtUiPlugin/customwidget.h>
class PyCustomWidget: public QObject, public QDesignerCustomWidgetInterface
{
#ifndef PY_CUSTOM_WIDGETS_H_
#define PY_CUSTOM_WIDGETS_H_
-#include <shiboken.h>
+#include <sbkpython.h>
-#include <QtUiPlugin/QDesignerCustomWidgetInterface>
+#include <QtUiPlugin/customwidget.h>
#include <QtCore/qlist.h>
# See libshiboken/CMakeLists.txt
if(PYTHON_LIMITED_API)
- target_compile_definitions(QtExampleIcons PRIVATE "-DPy_LIMITED_API=0x03080000")
+ target_compile_definitions(QtExampleIcons PRIVATE "-DPy_LIMITED_API=0x03090000")
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths(False)
-from PySide6.QtBluetooth import QBluetoothLocalDevice
+from PySide6.QtBluetooth import QBluetoothLocalDevice # noqa: E402
class QBluetoothLocalDeviceTest(unittest.TestCase):
def testInitialization(self):
- device = QBluetoothLocalDevice()
+ device = QBluetoothLocalDevice() # noqa: F841
if __name__ == '__main__':
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths(False)
-from PySide6.QtCore import QUuid
-from PySide6.QtBluetooth import (QBluetoothUuid, QLowEnergyServiceData,
- QLowEnergyDescriptorData,
- QLowEnergyCharacteristicData)
+from PySide6.QtCore import QUuid # noqa: E402
+from PySide6.QtBluetooth import (QBluetoothUuid, QLowEnergyServiceData, # noqa: E402
+ QLowEnergyDescriptorData, # noqa: E402
+ QLowEnergyCharacteristicData) # noqa: E402
class QLowEnergyCharacteristicsTest(unittest.TestCase):
PYSIDE_TEST(qfileread_test.py)
PYSIDE_TEST(qflags_test.py)
PYSIDE_TEST(qinstallmsghandler_test.py)
+PYSIDE_TEST(qiodevice_buffered_read_test.py)
+PYSIDE_TEST(qiopipe_test.py)
PYSIDE_TEST(qjsondocument_test.py)
PYSIDE_TEST(qlinef_test.py)
PYSIDE_TEST(qlocale_test.py)
PYSIDE_TEST(qpoint_test.py)
PYSIDE_TEST(qprocess_test.py)
PYSIDE_TEST(qproperty_decorator.py)
+PYSIDE_TEST(qrandomgenerator_test.py)
PYSIDE_TEST(qrect_test.py)
PYSIDE_TEST(qregularexpression_test.py)
PYSIDE_TEST(qresource_test.py)
PYSIDE_TEST(qsettings_test.py)
+PYSIDE_TEST(qsharedmemory_test.py)
PYSIDE_TEST(qsize_test.py)
PYSIDE_TEST(qslot_object_test.py)
PYSIDE_TEST(qsocketnotifier_test.py)
PYSIDE_TEST(versioninfo_test.py)
PYSIDE_TEST(loggingcategorymacros_test.py)
PYSIDE_TEST(qrunnable_test.py)
+PYSIDE_TEST(qmessage_logger_test.py)
if(X11)
PYSIDE_TEST(qhandle_test.py)
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
sys.path.append(os.fspath(Path(__file__).resolve().parents[1] / "util"))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths()
-from PySide6.QtCore import QObject
+from PySide6.QtCore import QObject # noqa: E402
class A(QObject):
class CoroutineRaisesStopIterationTestCase(unittest.TestCase):
def setUp(self):
- self.coroutine = demo_coroutine()
+ self.coroutine = demo_coroutine() # noqa: F821
def testCoroutine(self):
with self.assertRaises(StopIteration):
self.value = val
def testBug(self):
- app = QCoreApplication([])
+ app = QCoreApplication([]) # noqa: F841
bug = QBug()
self.value = ''
bug.done.connect(self.on_done)
class TestBug(unittest.TestCase):
def testCase(self):
- l = Lock()
+ l = Lock() # noqa: E741
l.tryLock() # this cause a assertion
+ l.unlock()
if __name__ == '__main__':
self.start = None
self.end = None
- app = QCoreApplication([])
+ app = QCoreApplication([]) # noqa: F841
model = MyModel()
model.columnsAboutToBeInserted.connect(self.mySlot)
model.columnsAboutToBeInserted.emit(QModelIndex(), 0, 1)
f.test = 1
set_counter = 0
- ret = f.test
+ ret = f.test # noqa: F841
self.assertEqual(get_counter, 1)
self.assertEqual(set_counter, 0)
def testSignalDestroyedinConnect(self):
# PYSIDE-2328: Connect to signal of temporary
- with self.assertRaises(RuntimeError):
+ with self.assertRaises(TypeError):
Foo().s.connect(None)
print("\n\n" + cm.exception.args[0])
self.assertTrue(self.probe in cm.exception.args[0])
with self.assertRaises(TypeError) as cm:
- qApp.quit_on_last_window_closed = object
+ qApp.quit_on_last_window_closed = object # noqa: F821
self.assertTrue(self.probe in cm.exception.args[0])
def testDocIsWorking(self):
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths(False)
-from PySide6.QtCore import QObject, SIGNAL
+from PySide6.QtCore import QObject, SIGNAL # noqa: E402
class MyObject(QObject):
"""), "<string>", "exec"), globals(), edict)
globals().update(edict)
feature_list = feature._really_all_feature_names
- func_list = [tst_bit0, tst_bit1, tst_bit2, tst_bit3,
- tst_bit4, tst_bit5, tst_bit6, tst_bit7]
+ func_list = [tst_bit0, tst_bit1, tst_bit2, tst_bit3, # noqa: F821
+ tst_bit4, tst_bit5, tst_bit6, tst_bit7] # noqa: F821
for idx in range(0x100):
feature.reset()
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths(False)
-import datetime
+import datetime # noqa: E402
-from PySide6.QtCore import QTime, QDateTime, QDate
+from PySide6.QtCore import QTime, QDateTime, QDate # noqa: E402
class TestDateTimeConversions (unittest.TestCase):
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths(False)
-from PySide6.QtCore import QCoreApplication, QParallelAnimationGroup, QSequentialAnimationGroup, QTimer
+from PySide6.QtCore import (QCoreApplication, QParallelAnimationGroup, # noqa: E402
+ QSequentialAnimationGroup, QTimer) # noqa: E402
class QAnimationGroupTest(unittest.TestCase):
'''Test case for operator QByteArray += on null QByteArrays'''
doc_prefix = 'Null object'
- doc_filter = lambda x: x.startswith('test')
+ doc_filter = lambda x: x.startswith('test') # noqa: E731
def setUp(self):
self.obj = QByteArray()
'''Test case for operator QByteArray += on valid QByteArrays'''
doc_prefix = 'Valid object'
- doc_filter = lambda x: x.startswith('test')
+ doc_filter = lambda x: x.startswith('test') # noqa: E731
def setUp(self):
self.obj = QByteArray(bytes('some byte array', "UTF-8"))
'''Test cases for buffered read methods of QIODevice'''
-from PySide6.QtCore import QBuffer
-
import enum
+import os
+import sys
import unittest
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+from PySide6.QtCore import QBuffer
+
class TestQIODeviceBufferedRead(unittest.TestCase):
class TestType(enum.Enum):
'''Test cases for the QIOPipe class'''
-from PySide6.QtCore import QIODevice, QIOPipe
-
+import os
+import sys
import unittest
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+
+from PySide6.QtCore import QIODevice, QIOPipe
+
class QIOPipeTest(unittest.TestCase):
def setUp(self) -> None:
value = en_locale.toString(3000000000)
self.assertEqual(value, "3,000,000,000")
value = en_locale.toString(10e40)
- self.assertEqual(value, "1E+41")
+ self.assertEqual(value.lower(), "1e+41")
if __name__ == '__main__':
lockFile = QLockFile(self._fileName)
self.assertTrue(lockFile.lock())
self.assertTrue(lockFile.isLocked())
- lock_info = lockFile.getLockInfo();
+ lock_info = lockFile.getLockInfo()
self.assertEqual(len(lock_info), 3)
self.assertEqual(lock_info[0], os.getpid())
lockFile.unlock()
--- /dev/null
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+import unittest
+import logging
+import io
+import sys
+import os
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+from PySide6.QtCore import QMessageLogger, QLoggingCategory, QtMsgType
+
+
+class QtMessageHandler(logging.Handler):
+ def __init__(self, category):
+ super().__init__()
+ self.category = category
+
+ def emit(self, record):
+ log_entry = self.format(record)
+ logger = QMessageLogger(__file__, record.lineno, record.funcName)
+
+ if record.levelno == logging.DEBUG:
+ if self.category.isDebugEnabled():
+ logger.debug(self.category, log_entry)
+ else:
+ logger.debug(log_entry)
+
+
+class TestQMessageLogger(unittest.TestCase):
+ def setUp(self):
+ self.logger = logging.getLogger("test_qmessagelogger")
+ self.logger.setLevel(logging.DEBUG)
+ self.stream = io.StringIO()
+ self.capture_handler = logging.StreamHandler(self.stream)
+ self.capture_handler.setLevel(logging.DEBUG)
+ self.logger.addHandler(self.capture_handler)
+
+ def tearDown(self):
+ self.logger.removeHandler(self.capture_handler)
+
+ def test_debug_with_category_enabled(self):
+ category_enabled = QLoggingCategory("test.category.enabled")
+ # 0 is QtDebugMsg
+ category_enabled.setEnabled(QtMsgType.QtDebugMsg, True)
+
+ qt_handler_enabled = QtMessageHandler(category_enabled)
+ self.logger.addHandler(qt_handler_enabled)
+
+ self.logger.debug("Debug with category enabled")
+ self.logger.removeHandler(qt_handler_enabled)
+
+ captured = self.stream.getvalue()
+ self.assertIn("Debug with category enabled", captured)
+
+ def test_debug_with_category_disabled(self):
+ category_disabled = QLoggingCategory("test.category.disabled")
+
+ qt_handler_disabled = QtMessageHandler(category_disabled)
+ self.logger.addHandler(qt_handler_disabled)
+
+ self.logger.debug("Debug with category disabled")
+ self.logger.removeHandler(qt_handler_disabled)
+
+ captured = self.stream.getvalue()
+ self.assertIn("Debug with category disabled", captured)
+
+
+if __name__ == "__main__":
+ unittest.main()
# 2- Create parent and childrens
# 3- While keeping the children alive, call parent.children()
# 4- Delete parent
- app = QCoreApplication([])
+ app = QCoreApplication([]) # noqa: F841
parent = QObject()
- children = [QObject(parent) for x in range(25)]
+ children = [QObject(parent) for x in range(25)] # noqa: F841
# Uncomment the lines below to make the test pass
# del children
# del child2
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths(False)
-from PySide6.QtCore import QObject
+from PySide6.QtCore import QObject # noqa: E402
class MyObject(QObject):
@unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
def testReference(self):
o = QObject()
+ base_ref_count = sys.getrefcount(o)
m = MyObject(o)
- self.assertEqual(sys.getrefcount(o), 3)
+ self.assertEqual(sys.getrefcount(o), base_ref_count + 1)
del m
- self.assertEqual(sys.getrefcount(o), 2)
+ self.assertEqual(sys.getrefcount(o), base_ref_count)
if __name__ == '__main__':
def __init__(self):
self.member = 'member'
super().__init__()
- obj0 = DerivedObject()
+ obj0 = DerivedObject() # noqa: F841
# The second instantiation of DerivedObject will generate an exception
# that will not come to surface immediately.
obj1 = DerivedObject()
@unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
def testParentDestructor(self):
parent = QObject()
- self.assertEqual(sys.getrefcount(parent), 2)
+ base_ref_count_parent = sys.getrefcount(parent)
child = QObject(parent)
- self.assertEqual(sys.getrefcount(child), 3)
- self.assertEqual(sys.getrefcount(parent), 2)
+ base_ref_count_child = sys.getrefcount(child)
+ self.assertEqual(sys.getrefcount(parent), base_ref_count_parent)
del parent
- self.assertEqual(sys.getrefcount(child), 2)
+ self.assertEqual(sys.getrefcount(child), base_ref_count_child - 1)
# this will fail because parent deleted child cpp object
self.assertRaises(RuntimeError, lambda: child.objectName())
@unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
def testMultipleChildren(self):
o = QObject()
- self.assertEqual(sys.getrefcount(o), 2)
+ base_ref_count_o = sys.getrefcount(o)
c = QObject(o)
- self.assertEqual(sys.getrefcount(c), 3)
- self.assertEqual(sys.getrefcount(o), 2)
+ base_ref_count_c = sys.getrefcount(c)
+ self.assertEqual(sys.getrefcount(o), base_ref_count_o)
c2 = QObject(o)
- self.assertEqual(sys.getrefcount(o), 2)
- self.assertEqual(sys.getrefcount(c), 3)
- self.assertEqual(sys.getrefcount(c2), 3)
+ self.assertEqual(sys.getrefcount(o), base_ref_count_o)
+ self.assertEqual(sys.getrefcount(c), base_ref_count_c)
+ self.assertEqual(sys.getrefcount(c2), base_ref_count_c)
del o
- self.assertEqual(sys.getrefcount(c), 2)
- self.assertEqual(sys.getrefcount(c2), 2)
+ self.assertEqual(sys.getrefcount(c), base_ref_count_c - 1)
+ self.assertEqual(sys.getrefcount(c2), base_ref_count_c - 1)
# this will fail because parent deleted child cpp object
self.assertRaises(RuntimeError, lambda: c.objectName())
@unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
def testRecursiveParent(self):
o = QObject()
- self.assertEqual(sys.getrefcount(o), 2)
+ base_ref_count_o = sys.getrefcount(o)
c = QObject(o)
- self.assertEqual(sys.getrefcount(c), 3)
- self.assertEqual(sys.getrefcount(o), 2)
+ base_ref_count_c = sys.getrefcount(c)
+ self.assertEqual(sys.getrefcount(o), base_ref_count_o)
c2 = QObject(c)
- self.assertEqual(sys.getrefcount(o), 2)
- self.assertEqual(sys.getrefcount(c), 3)
- self.assertEqual(sys.getrefcount(c2), 3)
+ base_ref_count_c2 = sys.getrefcount(c2)
+ self.assertEqual(sys.getrefcount(o), base_ref_count_o)
+ self.assertEqual(sys.getrefcount(c), base_ref_count_c)
del o
- self.assertEqual(sys.getrefcount(c), 2)
- self.assertEqual(sys.getrefcount(c2), 2)
+ self.assertEqual(sys.getrefcount(c), base_ref_count_c - 1)
+ self.assertEqual(sys.getrefcount(c2), base_ref_count_c2 - 1)
# this will fail because parent deleted child cpp object
self.assertRaises(RuntimeError, lambda: c.objectName())
@unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
def testParentTransfer(self):
o = QObject()
- self.assertEqual(sys.getrefcount(o), 2)
+ base_ref_count = sys.getrefcount(o)
c = QObject()
- self.assertEqual(sys.getrefcount(c), 2)
+ self.assertEqual(sys.getrefcount(c), base_ref_count)
c.setParent(o)
- self.assertEqual(sys.getrefcount(c), 3)
+ self.assertEqual(sys.getrefcount(c), base_ref_count + 1)
c.setParent(None)
- self.assertEqual(sys.getrefcount(c), 2)
+ self.assertEqual(sys.getrefcount(c), base_ref_count)
del c
del o
myProperty = Property(int, readP, fset=writeP, notify=notifyP)
+class OtherClass:
+ """Helper for QObjectWithOtherClassPropertyTest."""
+ pass
+
+
+class MyObjectWithOtherClassProperty(QObject):
+ """Helper for QObjectWithOtherClassPropertyTest."""
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._otherclass = None
+
+ def _get_otherclass(self):
+ return self._otherclass
+
+ def _set_otherclass(self, o):
+ self._otherclass = o
+
+ otherclass = Property(OtherClass, fget=_get_otherclass, fset=_set_otherclass)
+
+
class PropertyWithNotify(unittest.TestCase):
def called(self):
self.called_ = True
self.assertEqual(o.property("myProperty"), 10)
+class QObjectWithOtherClassPropertyTest(unittest.TestCase):
+ """PYSIDE-2193: For properties of custom classes not wrapped by shiboken,
+ QVariant<PyObjectWrapper> is used, which had refcount issues causing crashes.
+ Exercise the QVariant conversion by setting and retrieving via the
+ QVariant-based property()/setProperty() API."""
+ def testNotify(self):
+ obj = MyObjectWithOtherClassProperty()
+ obj.setProperty("otherclass", OtherClass())
+ for i in range(10):
+ pv = obj.property("otherclass")
+ print(pv) # Exercise repr
+ self.assertTrue(type(pv) is OtherClass)
+
+
if __name__ == '__main__':
unittest.main()
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths(False)
-from PySide6.QtCore import QObject, Property
+from PySide6.QtCore import QObject, Property # noqa: E402
class MyObject(QObject):
def testDecorator(self):
self._obDestroyed = False
o = MyObject()
- weak = weakref.ref(o, self.destroyCB)
+ weak = weakref.ref(o, self.destroyCB) # noqa: F841
o.value = 10
self.assertEqual(o._value, 10)
self.assertEqual(o.value, 10)
def testGenerator64(self):
generator = QRandomGenerator64()
- r = generator.generate()
+ r = generator.generate() # noqa: F841
if __name__ == '__main__':
'''Client for the unit test of QSharedMemory'''
+import os
import sys
-from PySide6.QtCore import QSharedMemory
+from pathlib import Path
+FILE = Path(__file__).resolve()
+sys.path.append(os.fspath(FILE.parents[1]))
+from init_paths import init_test_paths # noqa: E402
+init_test_paths(False)
+
+from PySide6.QtCore import QSharedMemory # noqa: E402
def read_string(shared_memory):
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths # noqa: E402
+from init_paths import init_test_paths # noqa: E402
init_test_paths(False)
from PySide6.QtCore import Property, QSize
self.assertEqual(len(v), 5)
self.assertEqual(type(v[0]), int)
self.assertEqual(type(v[1]), int)
- #self.assertEqual(type(v[2]), int)
+ self.assertEqual(type(v[2]), int)
self.assertEqual(type(v[3]), str)
self.assertEqual(type(v[4]), str)
PYSIDE_TEST(deepcopy_test.py)
PYSIDE_TEST(event_filter_test.py)
PYSIDE_TEST(float_to_int_implicit_conversion_test.py)
-PYSIDE_TEST(pyside_reload_test.py)
PYSIDE_TEST(qbrush_test.py)
PYSIDE_TEST(qcolor_test.py)
PYSIDE_TEST(qcolor_reduce_test.py)
endif()
PYSIDE_TEST(qitemselection_test.py)
PYSIDE_TEST(qpainter_test.py)
+PYSIDE_TEST(qpaintengine_test.py)
PYSIDE_TEST(qpen_test.py)
PYSIDE_TEST(qpdfwriter_test.py)
PYSIDE_TEST(qpixelformat_test.py)
class TestBug660(unittest.TestCase):
- '''QMimeData type deleted prematurely when overriding mime-type in QStandardItemModel drag and drop'''
+ '''QMimeData type deleted prematurely when overriding mime-type in QStandardItemModel
+ drag and drop'''
def testIt(self):
model = MyItemModel()
model.mimeData([model.index(0, 0)]) # if it doesn't raise an exception it's all right!
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
from __future__ import annotations
-'''Test cases for PYSIDE-344, imul/idiv are used instead of mul/div, modifying the argument passed in'''
+'''Test cases for PYSIDE-344, imul/idiv are used instead of mul/div,
+ modifying the argument passed in'''
import os
import sys
@unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
def testRefCount(self):
o = QObject()
+ base_ref_count = sys.getrefcount(o)
filt = MyFilter()
o.installEventFilter(filt)
- self.assertEqual(sys.getrefcount(o), 2)
+ self.assertEqual(sys.getrefcount(o), base_ref_count)
o.installEventFilter(filt)
- self.assertEqual(sys.getrefcount(o), 2)
+ self.assertEqual(sys.getrefcount(o), base_ref_count)
o.removeEventFilter(filt)
- self.assertEqual(sys.getrefcount(o), 2)
+ self.assertEqual(sys.getrefcount(o), base_ref_count)
def testObjectDestructorOrder(self):
w = QWindow()
+++ /dev/null
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-from __future__ import annotations
-
-import importlib
-import importlib.util
-import os
-import shutil
-import sys
-import unittest
-
-from pathlib import Path
-sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
-init_test_paths(False)
-
-
-orig_path = os.path.join(os.path.dirname(__file__))
-workdir = os.getcwd()
-src = os.path.normpath(os.path.join(orig_path, '..', 'QtWidgets', 'test_module_template.py'))
-dst = os.path.join(workdir, 'test_module.py')
-shutil.copyfile(src, dst)
-sys.path.append(workdir)
-
-
-def reload_module(moduleName):
- importlib.reload(moduleName)
-
-
-def increment_module_value():
- modfile = open(dst, 'a')
- modfile.write('Sentinel.value += 1' + os.linesep)
- modfile.flush()
- modfile.close()
- if not sys.dont_write_bytecode:
- import importlib.util
- cacheFile = importlib.util.cache_from_source(dst)
- os.remove(cacheFile)
-
-
-class TestModuleReloading(unittest.TestCase):
-
- def testModuleReloading(self):
- '''Test module reloading with on-the-fly modifications.'''
-
- import test_module
- self.assertEqual(test_module.Sentinel.value, 10)
-
- increment_module_value()
- reload_module(sys.modules['test_module'])
- self.assertEqual(test_module.Sentinel.value, 11)
-
- reload_module(sys.modules['test_module'])
- self.assertEqual(test_module.Sentinel.value, 11)
-
- increment_module_value()
- reload_module(sys.modules['test_module'])
- self.assertEqual(test_module.Sentinel.value, 12)
-
-
-if __name__ == "__main__":
- unittest.main()
self.assertEqual(len(families), 1)
self.assertEqual(families[0], font_name)
+ def testTagConstruction(self):
+ tag1 = QFont.Tag("head")
+ tag2 = QFont.Tag.fromValue(tag1.value())
+ self.assertEqual(tag1.value(), tag2.value())
+
if __name__ == '__main__':
unittest.main()
def testQIconCtorWithNone(self):
icon = QIcon(None)
- pixmap = icon.pixmap(48, 48)
+ pixmap = icon.pixmap(48, 48) # noqa: F841
self.app.exec()
--- /dev/null
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+import os
+import sys
+import unittest
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+from helper.usesqapplication import UsesQApplication
+from PySide6.QtGui import QPaintEngine, QPainter, QPaintDevice
+from PySide6.QtCore import QPoint, QRect, QLine
+
+
+class PaintEngine(QPaintEngine):
+ def __init__(self):
+ super().__init__()
+ self.line_count = 0
+ self.point_count = 0
+ self.rect_count = 0
+
+ def drawPoints(self, points):
+ self.point_count = len(points)
+
+ def drawRects(self, rects):
+ self.rect_count = len(rects)
+
+ def drawLines(self, lines):
+ self.line_count = len(lines)
+
+ def updateState(self, s):
+ pass
+
+ def begin(self, _dev):
+ return True
+
+ def end(self):
+ return True
+
+
+class PaintDevice(QPaintDevice):
+ def __init__(self):
+ super().__init__()
+ self._engine = PaintEngine()
+
+ def paintEngine(self):
+ return self._engine
+
+ def metric(self, metric):
+ if metric == QPaintDevice.PaintDeviceMetric.PdmDevicePixelRatioScaled:
+ return super().metric(metric)
+ return 1
+
+
+class QPaintEngineTest(UsesQApplication):
+ """PYSIDE-3002: test whether virtual functions of QPaintEngine taking
+ a C-style array of geometry primitives can be overridden."""
+ def setUp(self):
+ super().setUp()
+ self._paint_device = PaintDevice()
+
+ def tearDown(self):
+ self._paint_device = None
+
+ def test(self):
+ points = [QPoint(1, 2), QPoint(3, 4)]
+ rectangles = [QRect(1, 1, 1, 1), QRect(2, 2, 2, 2)]
+ lines = [QLine(1, 2, 3, 4), QLine(3, 4, 5, 6)]
+
+ with QPainter(self._paint_device) as painter:
+ painter.drawPoints(points)
+ painter.drawRects(rectangles)
+ painter.drawLines(lines)
+
+ engine = self._paint_device.paintEngine()
+ self.assertTrue(engine.line_count, 2)
+ self.assertTrue(engine.point_count, 2)
+ self.assertTrue(engine.rect_count, 2)
+
+
+if __name__ == '__main__':
+ unittest.main()
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths(False)
-from PySide6.QtGui import QPixmap
+from PySide6.QtGui import QPixmap # noqa: E402
-from helper.usesqapplication import UsesQApplication
+from helper.usesqapplication import UsesQApplication # noqa: E402
xpm = [
"27 22 206 2",
class QHelpEngineCreation(UsesQApplication):
def testConstructor(self):
- helpEngine = QHelpEngine('mycollection.qch')
+ helpEngine = QHelpEngine('mycollection.qch') # noqa: F841
if __name__ == '__main__':
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths(False)
-from PySide6.QtLocation import QGeoServiceProvider
+from PySide6.QtLocation import QGeoServiceProvider # noqa: E402
class QLocationTestCase(unittest.TestCase):
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths(False)
-from helper.usesqapplication import UsesQApplication
-from PySide6.QtMultimediaWidgets import QGraphicsVideoItem, QVideoWidget
-from PySide6.QtWidgets import QGraphicsScene, QGraphicsView, QVBoxLayout, QWidget
-from PySide6.QtCore import QTimer
+from helper.usesqapplication import UsesQApplication # noqa: E402
+from PySide6.QtMultimediaWidgets import QGraphicsVideoItem, QVideoWidget # noqa: E402
+from PySide6.QtWidgets import QGraphicsScene, QGraphicsView, QVBoxLayout, QWidget # noqa: E402
+from PySide6.QtCore import QTimer # noqa: E402
class MyWidget(QWidget):
w = MyWidget()
w.show()
- timer = QTimer.singleShot(100, self.app.quit)
+ timer = QTimer.singleShot(100, self.app.quit) # noqa: F841
self.app.exec()
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths(False)
-from PySide6.QtPositioning import QGeoPositionInfoSource
+from PySide6.QtPositioning import QGeoPositionInfoSource # noqa: E402
class QPositioningTestCase(unittest.TestCase):
PYSIDE_TEST(qqmlincubator_incubateWhile.py)
PYSIDE_TEST(qquickitem_grabToImage.py)
PYSIDE_TEST(signal_arguments.py)
+PYSIDE_TEST(signal_types.py)
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths(False)
-from PySide6.QtCore import (QCoreApplication, QUrl, QObject, Property)
-from PySide6.QtQml import (QQmlComponent, QQmlEngine, QmlAnonymous, QmlElement)
+from PySide6.QtCore import (QCoreApplication, QUrl, QObject, Property) # noqa: E402
+from PySide6.QtQml import (QQmlComponent, QQmlEngine, QmlAnonymous, QmlElement) # noqa: E402
QML_IMPORT_NAME = "grouped"
class TestQmlGroupedProperties(unittest.TestCase):
def testIt(self):
- app = QCoreApplication(sys.argv)
+ app = QCoreApplication(sys.argv) # noqa: F841
file = Path(__file__).resolve().parent / "groupedproperty.qml"
url = QUrl.fromLocalFile(file)
engine = QQmlEngine()
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths(False)
-from helper.helper import quickview_errorstring
-from helper.timedqguiapplication import TimedQGuiApplication
-from PySide6.QtCore import QTimer, QUrl
-from PySide6.QtGui import QColor
-from PySide6.QtQuick import QQuickItem, QQuickView
+from helper.helper import quickview_errorstring # noqa: E402
+from helper.timedqguiapplication import TimedQGuiApplication # noqa: E402
+from PySide6.QtCore import QTimer, QUrl # noqa: E402
+from PySide6.QtGui import QColor # noqa: E402
+from PySide6.QtQuick import QQuickItem, QQuickView # noqa: E402
class TestGrabToSharedPointerImage(TimedQGuiApplication):
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths(False)
-from PySide6.QtCore import (QCoreApplication, QUrl, QObject, Property)
-from PySide6.QtQml import (QQmlComponent, QQmlEngine, QmlAnonymous,
- QmlAttached, QmlElement, ListProperty,
- qmlAttachedPropertiesObject)
+from PySide6.QtCore import (QCoreApplication, QUrl, QObject, Property) # noqa: E402
+from PySide6.QtQml import (QQmlComponent, QQmlEngine, QmlAnonymous, # noqa: E402
+ QmlAttached, QmlElement, ListProperty, # noqa: E402
+ qmlAttachedPropertiesObject) # noqa: E402
QML_IMPORT_NAME = "TestLayouts"
class TestQmlAttached(unittest.TestCase):
def testIt(self):
- app = QCoreApplication(sys.argv)
+ app = QCoreApplication(sys.argv) # noqa: F841
file = Path(__file__).resolve().parent / 'registerattached.qml'
url = QUrl.fromLocalFile(file)
engine = QQmlEngine()
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths(False)
-from PySide6.QtCore import (QCoreApplication, QUrl, QObject,
- Property)
-from PySide6.QtQml import (QQmlComponent, QQmlEngine, QmlExtended,
- QmlElement)
+from PySide6.QtCore import (QCoreApplication, QUrl, QObject, # noqa: E402
+ Property) # noqa: E402
+from PySide6.QtQml import (QQmlComponent, QQmlEngine, QmlExtended, # noqa: E402
+ QmlElement) # noqa: E402
"""Test for the QmlExtended decorator. Extends a class TestWidget
class TestQmlExtended(unittest.TestCase):
def testIt(self):
- app = QCoreApplication(sys.argv)
+ app = QCoreApplication(sys.argv) # noqa: F841
file = Path(__file__).resolve().parent / 'registerextended.qml'
url = QUrl.fromLocalFile(file)
engine = QQmlEngine()
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths(False)
-from PySide6.QtCore import (QCoreApplication, QUrl)
-from PySide6.QtQml import (QQmlComponent, QQmlEngine,
- QmlElement, QPyQmlParserStatus)
+from PySide6.QtCore import (QCoreApplication, QUrl) # noqa: E402
+from PySide6.QtQml import (QQmlComponent, QQmlEngine, # noqa: E402
+ QmlElement, QPyQmlParserStatus) # noqa: E402
QML_IMPORT_NAME = "ParserStatus"
class TestQmlAttached(unittest.TestCase):
def testIt(self):
- app = QCoreApplication(sys.argv)
+ app = QCoreApplication(sys.argv) # noqa: F841
file = Path(__file__).resolve().parent / 'registerparserstatus.qml'
url = QUrl.fromLocalFile(file)
engine = QQmlEngine()
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths(False)
-from PySide6.QtCore import QDir, QUrl
-from PySide6.QtGui import QGuiApplication
-from PySide6.QtQml import qmlRegisterType
+from PySide6.QtCore import QDir, QUrl # noqa: E402
+from PySide6.QtGui import QGuiApplication # noqa: E402
+from PySide6.QtQml import qmlRegisterType # noqa: E402
class TestQmlSupport(unittest.TestCase):
def testIt(self):
- app = QGuiApplication([])
+ app = QGuiApplication([]) # noqa: F841
file = os.fspath(Path(__file__).resolve().parent / 'ModuleType.qml')
url = QUrl.fromLocalFile(QDir.fromNativeSeparators(file))
from PySide6.QtCore import Property, QTimer, QUrl, QObject, Slot # noqa: E402
from PySide6.QtGui import QGuiApplication # noqa: E402
-from PySide6.QtQml import (qmlRegisterSingletonType, qmlRegisterSingletonInstance,
+from PySide6.QtQml import (qmlRegisterSingletonType, qmlRegisterSingletonInstance, # noqa: E402
QmlElement, QmlSingleton, QJSValue) # noqa: E402
from PySide6.QtQuick import QQuickView # noqa: E402
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths(False)
-from helper.helper import quickview_errorstring
-from helper.timedqguiapplication import TimedQGuiApplication
+from helper.helper import quickview_errorstring # noqa: E402
+from helper.timedqguiapplication import TimedQGuiApplication # noqa: E402
-from PySide6.QtQuick import QQuickView
-from PySide6.QtCore import QObject, Signal, Slot, QUrl
-from PySide6.QtQml import QmlElement
+from PySide6.QtQuick import QQuickView # noqa: E402
+from PySide6.QtCore import QObject, Signal, Slot, QUrl # noqa: E402
+from PySide6.QtQml import QmlElement # noqa: E402
"""PYSIDE-2098: Roundtrip test for signals using QVariantList/QVariantMap.
self.dictSignal.emit(test_dict)
@Slot(list)
- def list_slot(self, l):
- self._last_data = l
- print("list_slot", l)
+ def list_slot(self, lst):
+ self._last_data = lst
+ print("list_slot", lst)
@Slot(dict)
def dict_slot(self, d):
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[2]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths(False)
-from pathlib import Path
-from PySide6.QtCore import QObject, Slot
-from PySide6.QtQml import QQmlEngine, qmlRegisterType
-from PySide6.QtQuickTest import QUICK_TEST_MAIN_WITH_SETUP
+from pathlib import Path # noqa: E402
+from PySide6.QtCore import QObject, Slot # noqa: E402
+from PySide6.QtQml import QQmlEngine, qmlRegisterType # noqa: E402
+from PySide6.QtQuickTest import QUICK_TEST_MAIN_WITH_SETUP # noqa: E402
"""Copy of the equivalent test in qtdeclarative."""
-# Please add some tests, here
+# Copyright (C) 2025 Ford Motor Company
+# SPDX-License-Identifier: BSD-3-Clause
+
+# FIXME: TypeError: Failed to generate default value. Error: name 'int' is not defined. Problematic code: int(2)
+if(NOT APPLE)
+PYSIDE_TEST(repfile_test.py)
+PYSIDE_TEST(dynamic_types_test.py)
+PYSIDE_TEST(integration_test.py)
+
+add_subdirectory(cpp_interop)
+endif()
--- /dev/null
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+find_package(Qt6 REQUIRED COMPONENTS Core RemoteObjects)
+
+add_executable(cpp_interop ${MOC_SOURCES} cpp_interop.cpp)
+set_target_properties(cpp_interop PROPERTIES AUTOMOC ON)
+
+target_link_libraries(cpp_interop PUBLIC
+ Qt6::Core
+ Qt6::RemoteObjects
+)
+
+# Add a custom target to build the C++ program
+add_custom_target(build_cpp_interop
+ COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target cpp_interop
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+)
+
+# Exclude windows (see cpp_interop.cpp)
+if(NOT WIN32)
+ PYSIDE_TEST(cpp_interop_test.py)
+endif()
\ No newline at end of file
--- /dev/null
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qsocketnotifier.h>
+#include <QtCore/qtimer.h>
+
+#include <QtRemoteObjects/qremoteobjectreplica.h>
+#include <QtRemoteObjects/qremoteobjectnode.h>
+
+#ifdef Q_OS_WIN
+# include <QtCore/qt_windows.h>
+# include <QtCore/qwineventnotifier.h>
+#endif // Q_OS_WIN
+
+#include <iostream>
+
+using namespace Qt::StringLiterals;
+
+class CommandReader : public QObject
+{
+ Q_OBJECT
+public:
+ explicit CommandReader(QObject *parent = nullptr) : QObject(parent)
+ {
+#ifndef Q_OS_WIN
+ auto *notifier = new QSocketNotifier(fileno(stdin), QSocketNotifier::Read, this);
+ connect(notifier, &QSocketNotifier::activated, this, &CommandReader::handleInput);
+#else
+ // FIXME: Does not work, signals triggers too often, the app is stuck in getline()
+ auto notifier = new QWinEventNotifier(GetStdHandle(STD_INPUT_HANDLE), this);
+ connect(notifier, &QWinEventNotifier::activated, this, &CommandReader::handleInput);
+#endif
+ }
+
+signals:
+ void started();
+
+private slots:
+ void handleInput()
+ {
+ std::string line;
+ if (!std::getline(std::cin, line))
+ return;
+
+ if (line == "quit") {
+ std::cerr << "harness: Received quit. Stopping harness event loop.\n";
+ QCoreApplication::quit();
+ } else if (line == "start") {
+ std::cerr << "harness: Received start. Initializing harness nodes.\n";
+ emit started();
+ } else {
+ std::cerr << "harness: Unknown command \"" << line << "\"\n";
+ }
+ }
+};
+
+class Runner : public QObject
+{
+ Q_OBJECT
+public:
+ Runner(const QUrl &url, const QString &repName, QObject *parent = nullptr)
+ : QObject(parent)
+ , m_url(url)
+ , m_repName(repName)
+ {
+ m_host.setObjectName("cpp_host");
+ if (!m_host.setHostUrl(QUrl("tcp://127.0.0.1:0"_L1))) {
+ qWarning() << "harness: setHostUrl failed: " << m_host.lastError() << m_host.hostUrl();
+ std::cerr << "harness: Fatal harness error.\n";
+ QCoreApplication::exit(-2);
+ }
+
+ m_node.setObjectName("cpp_node");
+ std::cout << "harness: Host url:" << m_host.hostUrl().toEncoded().constData() << '\n';
+ std::cout.flush();
+ }
+
+public slots:
+ void onStart()
+ {
+ m_node.connectToNode(m_url);
+ m_replica.reset(m_node.acquireDynamic(m_repName));
+ if (!m_replica->waitForSource(1000)) {
+ std::cerr << "harness: Failed to acquire replica.\n";
+ QCoreApplication::exit(-1);
+ }
+
+ m_host.enableRemoting(m_replica.get());
+ }
+
+private:
+ QUrl m_url;
+ QString m_repName;
+ QRemoteObjectHost m_host;
+ QRemoteObjectNode m_node;
+ std::unique_ptr<QRemoteObjectDynamicReplica> m_replica;
+};
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication a(argc, argv);
+ if (argc < 3) {
+ std::cerr << "Usage: " << argv[0] << " <url> <name of type>\n";
+ return -1;
+ }
+ QUrl url = QUrl::fromUserInput(QString::fromUtf8(argv[1]));
+ QString repName = QString::fromUtf8(argv[2]);
+
+ if (!url.isValid()) {
+ std::cerr << "Invalid URL: " << argv[1] << '\n';
+ return -1;
+ }
+
+ CommandReader reader;
+ Runner runner(url, repName);
+
+
+ QRemoteObjectNode node;
+ node.setObjectName("cpp_node");
+ std::unique_ptr<QRemoteObjectDynamicReplica> replica;
+
+ QObject::connect(&reader, &CommandReader::started, &runner, &Runner::onStart);
+
+ return QCoreApplication::exec();
+}
+
+#include "cpp_interop.moc"
--- /dev/null
+#!/usr/bin/python
+# Copyright (C) 2025 Ford Motor Company
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+'''Verify Python <--> C++ interop'''
+
+import os
+import sys
+import textwrap
+
+import unittest
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[2])) # For init_paths
+from init_paths import init_test_paths
+init_test_paths(False)
+
+from PySide6.QtCore import QUrl, QProcess, QObject, Signal
+from PySide6.QtRemoteObjects import (QRemoteObjectHost, QRemoteObjectNode, QRemoteObjectReplica,
+ RepFile)
+from PySide6.QtTest import QSignalSpy, QTest
+
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) # For wrap_tests_for_cleanup
+from test_shared import wrap_tests_for_cleanup
+from helper.usesqapplication import UsesQApplication
+
+
+"""
+This test needs to be run from the build directory in
+order to locate the harness binary.
+
+The previous tests all verify Remote Objects integration, but only
+using Python for both Source and Replica. We need to make sure there
+aren't any surprises in the interplay between Python and C++.
+
+This implements an initial test harness with a C++ app that is
+started by the Python unittest. We leverage the fact that Remote
+Objects can
+1) Allow remoting any QObject as a Source with enableRemoting
+2) Acquire Dynamic Replicas, where the definition needed for the
+ Replica is sent from the source.
+
+With these, we can create a working C++ app that doesn't need to be
+compiled with any information about the types being used. We have
+a host node in Python that shares a class derived from a RepFile
+Source type. The address of this node is passed to the C++ app via
+QProcess, and there a C++ node connects to that address to acquire
+(dynamically) a replica of the desired object.
+
+The C++ code also creates a host node and sends the address/port
+back to Python via the QProcess interface. Once the Python code
+receives the C++ side address and port, it connects a node to that
+URL and acquires the RepFile based type from Python.
+
+Python C++
+Host -----> Node (Dynamic acquire)
+ |
+ | Once initialized, the dynamic replica is
+ | shared (enable_remoting) from the C++ Host
+ |
+Node <----- Host
+"""
+
+
+def msg_cannot_start(process, executable):
+ return ('Cannot start "' + executable + '" in "'
+ + os.fspath(Path.cwd()) + '": ' + process.errorString())
+
+
+def stop_process(process):
+ result = process.waitForFinished(2000)
+ if not result:
+ process.kill()
+ result = process.waitForFinished(2000)
+ return result
+
+
+class Controller(QObject):
+ ready = Signal()
+
+ def __init__(self, utest: unittest.TestCase):
+ super().__init__()
+ # Store utest so we can make assertions
+ self.utest = utest
+
+ # Set up nodes
+ self.host = QRemoteObjectHost()
+ self.host.setObjectName("py_host")
+ self.host.setHostUrl(QUrl("tcp://127.0.0.1:0"))
+ self.cpp_url = None
+ self.node = QRemoteObjectNode()
+ self.node.setObjectName("py_node")
+ self._executable = "cpp_interop.exe" if os.name == "nt" else "./cpp_interop"
+
+ def start(self):
+ # Start the C++ application
+ self.process = QProcess()
+ self.process.readyReadStandardOutput.connect(self.process_harness_output)
+ self.process.readyReadStandardError.connect(self.process_harness_stderr_output)
+ urls = self.host.hostUrl().toDisplayString()
+ print(f'Starting C++ application "{self._executable}" "{urls}"', file=sys.stderr)
+ self.process.start(self._executable, [self.host.hostUrl().toDisplayString(), "Simple"])
+ self.utest.assertTrue(self.process.waitForStarted(2000),
+ msg_cannot_start(self.process, self._executable))
+
+ # Wait for the C++ application to output the host url
+ spy = QSignalSpy(self.ready)
+ self.utest.assertTrue(spy.wait(1000))
+ self.utest.assertTrue(self.cpp_url.isValid())
+
+ self.utest.assertTrue(self.node.connectToNode(self.cpp_url))
+ return True
+
+ def stop(self):
+ if self.process.state() == QProcess.ProcessState.Running:
+ print(f'Stopping C++ application "{self._executable}" {self.process.processId()}',
+ file=sys.stderr)
+ self.process.write("quit\n".encode())
+ self.process.closeWriteChannel()
+ self.utest.assertTrue(stop_process(self.process))
+ self.utest.assertEqual(self.process.exitStatus(), QProcess.ExitStatus.NormalExit)
+
+ def add_source(self, Source, Replica):
+ """
+ Source and Replica are types.
+
+ Replica is from the rep file
+ Source is a class derived from the rep file's Source type
+ """
+ self.process.write("start\n".encode())
+ source = Source()
+ self.host.enableRemoting(source)
+ replica = self.node.acquire(Replica)
+ self.utest.assertTrue(replica.waitForSource(5000))
+ self.utest.assertEqual(replica.state(), QRemoteObjectReplica.State.Valid)
+ return source, replica
+
+ def process_harness_output(self):
+ '''Process stdout from the C++ application, parse for URL'''
+ output = self.process.readAllStandardOutput().trimmed()
+ lines = output.data().decode().split("\n")
+ HOST_LINE = "harness: Host url:"
+ for line in lines:
+ print(" stdout: ", line, file=sys.stderr)
+ if line.startswith(HOST_LINE):
+ urls = line[len(HOST_LINE):].strip()
+ print(f'url="{urls}"', file=sys.stderr)
+ self.cpp_url = QUrl(urls)
+ self.ready.emit()
+
+ def process_harness_stderr_output(self):
+ '''Print stderr from the C++ application'''
+ output = self.process.readAllStandardError().trimmed()
+ print(" stderr: ", output.data().decode())
+
+
+class HarnessTest(UsesQApplication):
+ def setUp(self):
+ super().setUp()
+ self.rep = RepFile(self.__class__.contents)
+ self.controller = Controller(self)
+ self.assertTrue(self.controller.start())
+
+ def tearDown(self):
+ self.controller.stop()
+ self.app.processEvents()
+ super().tearDown()
+ QTest.qWait(100) # Wait for 100 msec
+
+
+@wrap_tests_for_cleanup(extra=['rep'])
+class TestBasics(HarnessTest):
+ contents = textwrap.dedent("""\
+ class Simple
+ {
+ PROP(int i = 2);
+ PROP(float f = -1. READWRITE);
+ }
+ """)
+
+ def compare_properties(self, instance, values):
+ '''Compare properties of instance with values'''
+ self.assertEqual(instance.i, values[0])
+ self.assertAlmostEqual(instance.f, values[1], places=5)
+
+ def testInitialization(self):
+ '''Test constructing RepFile from a path string'''
+ class Source(self.rep.source["Simple"]):
+ pass
+ source, replica = self.controller.add_source(Source, self.rep.replica["Simple"])
+ self.compare_properties(source, [2, -1])
+ self.compare_properties(replica, [2, -1])
+
+
+if __name__ == '__main__':
+ unittest.main()
--- /dev/null
+#!/usr/bin/python
+# Copyright (C) 2025 Ford Motor Company
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+'''Test cases for dynamic source/replica types'''
+
+import os
+import sys
+import unittest
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+from PySide6.QtRemoteObjects import RepFile
+
+from test_shared import wrap_tests_for_cleanup
+
+
+contents = """
+class Simple
+{
+ PROP(int i = 2);
+ PROP(float f = -1. READWRITE);
+ SIGNAL(random(int i));
+ SLOT(void reset());
+};
+"""
+
+
+@wrap_tests_for_cleanup(extra=['rep_file'])
+class QDynamicReplicas(unittest.TestCase):
+ '''Test case for dynamic Replicas'''
+
+ def setUp(self):
+ '''Set up test environment'''
+ self.rep_file = RepFile(contents)
+
+ def testDynamicReplica(self):
+ '''Verify that a valid Replica is created'''
+ Replica = self.rep_file.replica["Simple"]
+ self.assertIsNotNone(Replica)
+ replica = Replica()
+ self.assertIsNotNone(replica)
+ self.assertIsNotNone(replica.metaObject())
+ meta = replica.metaObject()
+ self.assertEqual(meta.className(), "Simple")
+ self.assertEqual(meta.superClass().className(), "QRemoteObjectReplica")
+ i = meta.indexOfProperty("i")
+ self.assertNotEqual(i, -1)
+ self.assertEqual(replica.propAsVariant(0), int(2))
+ self.assertEqual(replica.propAsVariant(1), float(-1.0))
+ self.assertEqual(replica.i, int(2))
+ self.assertEqual(replica.f, float(-1.0))
+
+
+@wrap_tests_for_cleanup(extra=['rep_file'])
+class QDynamicSources(unittest.TestCase):
+ '''Test case for dynamic Sources'''
+
+ def setUp(self):
+ '''Set up test environment'''
+ self.rep_file = RepFile(contents)
+ self.test_val = 0
+
+ def on_changed(self, val):
+ self.test_val = val
+
+ def testDynamicSource(self):
+ '''Verify that a valid Source is created'''
+ Source = self.rep_file.source["Simple"]
+ self.assertIsNotNone(Source)
+ source = Source()
+ self.assertIsNotNone(source)
+ self.assertIsNotNone(source.metaObject())
+ meta = source.metaObject()
+ self.assertEqual(meta.className(), "SimpleSource")
+ self.assertEqual(meta.superClass().className(), "QObject")
+ i = meta.indexOfProperty("i")
+ self.assertNotEqual(i, -1)
+ self.assertIsNotNone(source.__dict__.get('__PROPERTIES__'))
+ self.assertEqual(source.i, int(2))
+ self.assertEqual(source.f, float(-1.0))
+ source.iChanged.connect(self.on_changed)
+ source.fChanged.connect(self.on_changed)
+ source.i = 7
+ self.assertEqual(source.i, int(7))
+ self.assertEqual(self.test_val, int(7))
+ source.i = 3
+ self.assertEqual(self.test_val, int(3))
+ source.f = 3.14
+ self.assertAlmostEqual(self.test_val, float(3.14), places=5)
+
+
+if __name__ == '__main__':
+ unittest.main()
--- /dev/null
+#!/usr/bin/python
+# Copyright (C) 2025 Ford Motor Company
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+'''Test cases for basic Source/Replica communication'''
+
+import os
+import sys
+import textwrap
+import enum
+import gc
+
+import unittest
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+from PySide6.QtCore import QUrl, qWarning
+from PySide6.QtRemoteObjects import (QRemoteObjectHost, QRemoteObjectNode, QRemoteObjectReplica,
+ QRemoteObjectPendingCall, RepFile, getCapsuleCount)
+from PySide6.QtTest import QSignalSpy, QTest
+
+from test_shared import wrap_tests_for_cleanup
+from helper.usesqapplication import UsesQApplication
+
+contents = """
+class Simple
+{
+ PROP(int i = 2);
+ PROP(float f = -1. READWRITE);
+ SIGNAL(random(int i));
+ SLOT(void reset());
+ SLOT(int add(int i));
+};
+"""
+
+
+class QBasicTest(UsesQApplication):
+ '''Test case for basic source/replica communication'''
+ def setUp(self):
+ # Separate output to make debugging easier
+ qWarning(f"\nSet up {self.__class__.__qualname__}")
+ super().setUp()
+ '''Set up test environment'''
+ if hasattr(self.__class__, "contents"):
+ qWarning(f"Using class contents >{self.__class__.contents}<")
+ self.rep = RepFile(self.__class__.contents)
+ else:
+ self.rep = RepFile(contents)
+ self.host = QRemoteObjectHost(QUrl("tcp://127.0.0.1:0"))
+ self.host.setObjectName("host")
+ self.node = QRemoteObjectNode()
+ self.node.setObjectName("node")
+ self.node.connectToNode(self.host.hostUrl()) # pick up the url with the assigned port
+
+ def compare_properties(self, instance, values):
+ '''Compare properties of instance with values'''
+ self.assertEqual(instance.i, values[0])
+ self.assertAlmostEqual(instance.f, values[1], places=5)
+
+ def default_setup(self):
+ '''Set up default test environment'''
+ replica = self.node.acquire(self.rep.replica["Simple"])
+ # Make sure the replica is initialized with default values
+ self.compare_properties(replica, [2, -1])
+ self.assertEqual(replica.isInitialized(), False)
+ source = self.rep.source["Simple"]()
+ # Make sure the source is initialized with default values
+ self.compare_properties(source, [2, -1])
+ return replica, source
+
+ def tearDown(self):
+ self.assertEqual(getCapsuleCount(), 0)
+ self.app.processEvents()
+ super().tearDown()
+ # Separate output to make debugging easier
+ qWarning(f"Tore down {self.__class__.__qualname__}\n")
+
+
+@wrap_tests_for_cleanup(extra=['rep', 'host', 'node'])
+class ReplicaInitialization(QBasicTest):
+ def test_ReplicaInitialization(self):
+ replica, source = self.default_setup()
+ source.i = -1
+ source.f = 3.14
+ self.compare_properties(source, [-1, 3.14])
+ init_spy = QSignalSpy(replica.initialized)
+ self.host.enableRemoting(source)
+ self.assertEqual(replica.waitForSource(1000), True)
+ self.assertEqual(replica.state(), QRemoteObjectReplica.State.Valid)
+ # Make sure the replica values are updated to the source values
+ self.compare_properties(replica, [-1, 3.14])
+ self.assertEqual(init_spy.count(), 1)
+ self.assertEqual(replica.isInitialized(), True)
+
+
+@wrap_tests_for_cleanup(extra=['rep', 'host', 'node'])
+class SourcePropertyChange(QBasicTest):
+ def test_SourcePropertyChange(self):
+ replica, source = self.default_setup()
+ self.host.enableRemoting(source)
+ self.assertEqual(replica.waitForSource(1000), True)
+ # Make sure the replica values are unchanged since the source had the same values
+ self.compare_properties(replica, [2, -1])
+ source_spy = QSignalSpy(source.iChanged)
+ replica_spy = QSignalSpy(replica.iChanged)
+ source.i = 42
+ self.assertEqual(source_spy.count(), 1)
+ # Make sure the source value is updated
+ self.compare_properties(source, [42, source.f])
+ self.assertTrue(replica_spy.wait(1000))
+ self.assertEqual(replica_spy.count(), 1)
+ # Make sure the replica value is updated
+ self.compare_properties(replica, [42, replica.f])
+
+
+@wrap_tests_for_cleanup(extra=['rep', 'host', 'node'])
+class ReplicaPropertyChange(QBasicTest):
+ def test_ReplicaPropertyChange(self):
+ replica, source = self.default_setup()
+ self.host.enableRemoting(source)
+ self.assertEqual(replica.waitForSource(1000), True)
+ # Make sure push methods are working
+ source_spy = QSignalSpy(source.iChanged)
+ replica_spy = QSignalSpy(replica.iChanged)
+ replica.pushI(11)
+ # # Let eventloop run to update the source and verify the values
+ self.assertTrue(source_spy.wait(1000))
+ self.assertEqual(source_spy.count(), 1)
+ self.compare_properties(source, [11, source.f])
+ # Let eventloop run to update the replica and verify the values
+ self.assertTrue(replica_spy.wait(1000))
+ self.assertEqual(replica_spy.count(), 1)
+ self.compare_properties(replica, [11, replica.f])
+
+ # Test setter on replica
+ source_spy = QSignalSpy(source.fChanged)
+ replica_spy = QSignalSpy(replica.fChanged)
+ replica.f = 4.2
+ # Make sure the replica values are ** NOT CHANGED ** since the eventloop hasn't run
+ self.compare_properties(replica, [11, -1])
+ # Let eventloop run to update the source and verify the values
+ self.assertTrue(source_spy.wait(1000))
+ self.assertEqual(source_spy.count(), 1)
+ self.compare_properties(source, [source.i, 4.2])
+ # Let eventloop run to update the replica and verify the values
+ self.assertTrue(replica_spy.wait(1000))
+ self.assertEqual(replica_spy.count(), 1)
+ self.compare_properties(replica, [replica.i, 4.2])
+
+
+@wrap_tests_for_cleanup(extra=['rep', 'host', 'node'])
+class DerivedReplicaPropertyChange(QBasicTest):
+ def test_DerivedReplicaPropertyChange(self):
+ # Don't use default_setup(), instead create a derived replica
+ Replica = self.rep.replica["Simple"]
+
+ class DerivedReplica(Replica):
+ pass
+
+ replica = self.node.acquire(DerivedReplica)
+ # Make sure the replica is initialized with default values
+ self.compare_properties(replica, [2, -1])
+ self.assertEqual(replica.isInitialized(), False)
+ source = self.rep.source["Simple"]()
+ self.host.enableRemoting(source)
+ self.assertEqual(replica.waitForSource(1000), True)
+
+
+@wrap_tests_for_cleanup(extra=['rep', 'host', 'node'])
+class ReplicaSlotNotImplementedChange(QBasicTest):
+ def test_ReplicaSlotNotImplementedChange(self):
+ replica, source = self.default_setup()
+ self.host.enableRemoting(source)
+ self.assertEqual(replica.waitForSource(1000), True)
+ # Ideally this would fail as the slot is not implemented on the source
+ res = replica.reset()
+ self.assertEqual(type(res), type(None))
+ QTest.qWait(100) # Wait for 100 ms for async i/o. There isn't a signal to wait on
+ res = replica.add(5)
+ self.assertEqual(type(res), QRemoteObjectPendingCall)
+
+
+@wrap_tests_for_cleanup(extra=['rep', 'host', 'node'])
+class ReplicaSlotImplementedChange(QBasicTest):
+ def test_ReplicaSlotImplementedChange(self):
+ replica = self.node.acquire(self.rep.replica["Simple"])
+ replica.setObjectName("replica")
+
+ class Source(self.rep.source["Simple"]):
+ def __init__(self):
+ super().__init__()
+ self.i = 6
+ self.f = 3.14
+
+ def reset(self):
+ self.i = 0
+ self.f = 0
+
+ def add(self, i):
+ return self.i + i
+
+ source = Source()
+ source.setObjectName("source")
+ self.host.enableRemoting(source)
+ self.assertEqual(replica.waitForSource(1000), True)
+ self.compare_properties(source, [6, 3.14])
+ self.compare_properties(replica, [6, 3.14])
+ replica_spy = QSignalSpy(replica.iChanged)
+ res = replica.reset()
+ self.assertEqual(type(res), type(None))
+ self.assertEqual(replica_spy.wait(1000), True)
+ self.compare_properties(source, [0, 0])
+ self.compare_properties(replica, [0, 0])
+ res = replica.add(5)
+ self.assertEqual(type(res), QRemoteObjectPendingCall)
+ self.assertEqual(res.waitForFinished(1000), True)
+ self.assertEqual(res.returnValue(), 5)
+
+
+@wrap_tests_for_cleanup(extra=['rep', 'host', 'node'])
+class RefCountTest(QBasicTest):
+ contents = textwrap.dedent("""\
+ POD MyPOD{
+ ENUM class Position : unsigned short {position1=1, position2=2, position3=4}
+ Position pos,
+ QString name
+ }
+ class Simple
+ {
+ ENUM Position {Left, Right, Top, Bottom}
+ PROP(MyPOD myPod);
+ PROP(Position pos);
+ }
+ """)
+
+ def test_RefCount(self):
+ # Once the rep file is loaded, we should be tracking 4 converter capsules
+ # - 1 for the POD itself
+ # - 1 for the enum in the POD
+ # - 1 for the enum in the Source
+ # - 1 for the enum in the Replica
+ # We should be tracking 3 qobject capsules (POD, Replica, Source)
+ # Note: Source and Replica are distinct types, so Source::EPosition and
+ # Replica::EPosition are distinct as well.
+ # Note 2: The name of the enum ("Position") can be reused for different
+ # types in different classes as shown above.
+ self.assertEqual(getCapsuleCount(), 7)
+ MyPod = self.rep.pod["MyPOD"]
+ self.assertTrue(isinstance(MyPod, type))
+ self.assertTrue(issubclass(MyPod, tuple))
+ MyEnum = MyPod.get_enum("Position")
+ self.assertTrue(isinstance(MyEnum, type))
+ self.assertTrue(issubclass(MyEnum, enum.Enum))
+ e = MyEnum(4) # noqa: F841
+ Source = self.rep.source["Simple"]
+ source = Source() # noqa: F841
+ source = None # noqa: F841
+ Source = None
+ Replica = self.rep.replica["Simple"]
+ replica = self.node.acquire(Replica) # noqa: F841
+ replica = None # noqa: F841
+ Replica = None
+ MyEnum = None
+ MyPod = None
+ self.rep = None
+ e = None # noqa: F841
+ gc.collect()
+ # The enum and POD capsules will only be deleted (garbage collected) if
+ # the types storing them (RepFile, Replica and Source) are garbage
+ # collected first.
+ self.assertEqual(getCapsuleCount(), 0)
+
+
+@wrap_tests_for_cleanup(extra=['rep', 'host', 'node'])
+class EnumTest(QBasicTest):
+ contents = textwrap.dedent("""\
+ POD MyPOD{
+ ENUM class Position : unsigned short {position1=1, position2=2, position3=4}
+ Position pos,
+ QString name
+ }
+ class Simple
+ {
+ ENUM Position {Left, Right, Top, Bottom}
+ PROP(MyPOD myPod);
+ PROP(Position pos);
+ }
+ """)
+
+ def test_Enum(self):
+ MyPod = self.rep.pod["MyPOD"]
+ self.assertTrue(isinstance(MyPod, type))
+ self.assertTrue(issubclass(MyPod, tuple))
+ PodEnum = MyPod.get_enum("Position")
+ self.assertTrue(isinstance(PodEnum, type))
+ self.assertTrue(issubclass(PodEnum, enum.Enum))
+ t = (PodEnum(4), "test")
+ myPod = MyPod(*t)
+ with self.assertRaises(ValueError):
+ myPod = MyPod(PodEnum(0), "thing") # 0 isn't a valid enum value
+ myPod = MyPod(PodEnum(2), "thing")
+ self.assertEqual(myPod.pos, PodEnum.position2)
+ replica = self.node.acquire(self.rep.replica["Simple"])
+ replica.setObjectName("replica")
+ source = self.rep.source["Simple"]()
+ source.setObjectName("source")
+ source.myPod = (PodEnum.position2, "Hello")
+ SourceEnum = source.get_enum("Position")
+ self.assertTrue(isinstance(SourceEnum, type))
+ self.assertTrue(issubclass(SourceEnum, enum.Enum))
+ source.pos = SourceEnum.Top
+ self.assertEqual(source.myPod, (PodEnum.position2, "Hello"))
+ self.assertNotEqual(source.pos, 2)
+ self.host.enableRemoting(source)
+ self.assertEqual(replica.waitForSource(1000), True)
+ self.assertEqual(replica.myPod, (PodEnum.position2, "Hello"))
+ ReplicaEnum = replica.get_enum("Position")
+ # Test invalid comparisons
+ self.assertNotEqual(replica.pos, 2)
+ self.assertNotEqual(replica.pos, SourceEnum.Top)
+ self.assertNotEqual(replica.myPod, (SourceEnum(2), "Hello"))
+ self.assertNotEqual(replica.myPod, (ReplicaEnum(2), "Hello"))
+ self.assertNotEqual(replica.myPod, (2, "Hello"))
+ # Test valid comparisons to Replica enum
+ self.assertEqual(replica.pos, ReplicaEnum.Top)
+ self.assertEqual(replica.myPod, (PodEnum(2), "Hello"))
+
+
+@wrap_tests_for_cleanup(extra=['rep', 'host', 'node'])
+class PodTest(QBasicTest):
+ contents = textwrap.dedent("""\
+ POD MyPod(int i, QString s)
+
+ class Simple
+ {
+ PROP(MyPod pod);
+ }
+ """)
+
+ def test_Pod(self):
+ MyPod = self.rep.pod["MyPod"]
+ self.assertTrue(isinstance(MyPod, type))
+ self.assertTrue(issubclass(MyPod, tuple))
+ source = self.rep.source["Simple"]()
+ t = (42, "Hello")
+ pod = MyPod(*t)
+ source.pod = t
+ self.assertEqual(source.pod, t)
+ self.assertEqual(source.pod, pod)
+ source.pod = pod
+ self.assertEqual(source.pod, t)
+ self.assertEqual(source.pod, pod)
+ with self.assertRaises(ValueError):
+ source.pod = (11, "World", "!")
+ with self.assertRaises(TypeError):
+ source.pod = MyPod("Hello", "World")
+ self.assertEqual(source.pod, pod)
+ self.assertTrue(isinstance(pod, MyPod))
+ self.assertEqual(pod.i, 42)
+ self.assertEqual(pod.s, "Hello")
+ self.assertTrue(isinstance(source.pod, MyPod))
+
+
+if __name__ == '__main__':
+ unittest.main()
--- /dev/null
+#!/usr/bin/python
+# Copyright (C) 2025 Ford Motor Company
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+'''Test cases for RepFile'''
+
+import os
+import sys
+import unittest
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+from PySide6.QtRemoteObjects import RepFile
+
+from test_shared import wrap_tests_for_cleanup
+
+contents = """
+class Simple
+{
+ PROP(int i = 2);
+ PROP(float f = -1. READWRITE);
+ SIGNAL(random(int i));
+ SLOT(void reset());
+};
+"""
+
+
+@wrap_tests_for_cleanup()
+class QRepFileConstructor(unittest.TestCase):
+ '''Test case for RepFile constructors'''
+ expected = "RepFile(Classes: [Simple], PODs: [])"
+
+ def setUp(self):
+ '''Set up test environment'''
+ self.cwd = Path(__file__).parent
+ self.path = self.cwd / "simple.rep"
+
+ def testRepFileFromPath(self):
+ '''Test constructing RepFile from a path'''
+ with open(self.path, 'r') as f:
+ rep_file = RepFile(f.read())
+ self.assertEqual(str(rep_file), self.expected)
+
+ def testRepFileFromString(self):
+ '''Test constructing RepFile from a string'''
+ rep_file = RepFile(contents)
+ self.assertEqual(str(rep_file), self.expected)
+
+ def testRepFileInvalidString(self):
+ '''Test constructing RepFile from a string'''
+ with self.assertRaises(RuntimeError) as result:
+ RepFile("\n\n}\n\n")
+ self.assertEqual(str(result.exception),
+ "Error parsing input, line 3: error: Unknown token encountered")
+
+ def testRepFileNoArguments(self):
+ '''Test constructing RepFile with no arguments'''
+ with self.assertRaises(TypeError):
+ RepFile()
+
+
+if __name__ == '__main__':
+ unittest.main()
--- /dev/null
+class Simple
+{
+ PROP(int i = 2);
+ PROP(float f = -1. READWRITE);
+ SIGNAL(random(int i));
+ SLOT(void reset());
+};
--- /dev/null
+# Copyright (C) 2025 Ford Motor Company
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+import gc
+import sys
+from functools import wraps
+
+
+def _cleanup_local_variables(self, extra, debug):
+ """
+ Function to clean up local variables after a unit test.
+
+ This method will set any local variables defined in the test run to None. It also
+ sets variables of self to None, if they are provided in the extra list.
+
+ The self argument is passed by the decorator, so we can access the instance variables.
+ """
+ local_vars = self._locals
+ if debug:
+ print(f" Cleaning up locals: {local_vars.keys()} and member of self: {extra}",
+ file=sys.stderr)
+ exclude_vars = {'__builtins__', 'self', 'args', 'kwargs'}
+ for var in list(local_vars.keys()):
+ if var not in exclude_vars:
+ local_vars[var] = None
+ if debug:
+ print(f" Set {var} to None", file=sys.stderr)
+ # Remove variables added to 'self' during our test
+ for var in list(vars(self).keys()):
+ if var in extra:
+ setattr(self, var, None)
+ if debug:
+ print(f" Set self.{var} to None", file=sys.stderr)
+ gc.collect()
+
+
+# This leverages the tip from # https://stackoverflow.com/a/9187022/169296
+# for capturing local variables using sys.setprofile and a tracer function
+def wrap_tests_for_cleanup(extra: str | list[str] = None, debug: bool = False):
+ """
+ Method that returns a decorator for setting variables used in a test to
+ None, thus allowing the garbage collection to clean up properly and ensure
+ destruction behavior is correct. Using a method to return the decorator
+ allows us to pass extra arguments to the decorator, in this case for extra
+ data members on `self` to set to None or whether to output additional debug
+ logging.
+
+ It simply returns the class decorator to be used.
+ """
+ def decorator(cls):
+ """
+ This is a class decorator that finds and wraps all test methods in a
+ class.
+
+ The provided extra is used to define a set() of variables that are set
+ to None on `self` after the test method has run. This is useful for
+ making sure the local and self variables can be garbage collected.
+ """
+ _extra = set()
+ if extra:
+ if isinstance(extra, str):
+ _extra.add(extra)
+ else:
+ _extra.update(extra)
+ for name, attr in cls.__dict__.items():
+ if name.startswith("test") and callable(attr):
+ """
+ Only wrap methods that start with 'test' and are callable.
+ """
+ def make_wrapper(method):
+ """
+ This is the actual wrapper that will be used to wrap the
+ test methods. It will set a tracer function to capture the
+ local variables and then calls our cleanup function to set
+ the variables to None.
+ """
+ @wraps(method)
+ def wrapper(self, *args, **kwargs):
+ if debug:
+ print(f"wrap_tests_for_cleanup - calling {method.__name__}",
+ file=sys.stderr)
+
+ def tracer(frame, event, arg):
+ if event == 'return':
+ self._locals = frame.f_locals.copy()
+
+ # tracer is activated on next call, return or exception
+ sys.setprofile(tracer)
+ try:
+ # trace the function call
+ return method(self, *args, **kwargs)
+ finally:
+ # disable tracer and replace with old one
+ sys.setprofile(None)
+ # call our cleanup function
+ _cleanup_local_variables(self, _extra, debug)
+ if debug:
+ print(f"wrap_tests_for_cleanup - done calling {method.__name__}",
+ file=sys.stderr)
+ return wrapper
+ setattr(cls, name, make_wrapper(attr))
+ return cls
+ return decorator
+
+
+if __name__ == "__main__":
+ # Set up example test class
+ @wrap_tests_for_cleanup(extra="name", debug=True)
+ class test:
+ def __init__(self):
+ self.name = "test"
+
+ def testStuff(self):
+ value = 42
+ raise ValueError("Test")
+ temp = 11 # noqa: F841
+ return value
+
+ t = test()
+ try:
+ t.testStuff()
+ except ValueError:
+ pass
+ # Should print that `value` and `self.name` are set to None, even with the
+ # exception being raised.
-# Please add some tests, here
+PYSIDE_TEST(serialbus.py)
--- /dev/null
+{
+ "files": ["serialbus.py"]
+}
--- /dev/null
+#!/usr/bin/python
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+'''Test cases for QtSerialBus'''
+
+import os
+import sys
+import unittest
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths # noqa: E402
+init_test_paths(False)
+
+from PySide6.QtCore import QFile # noqa: E402
+from PySide6.QtSerialBus import QCanDbcFileParser # noqa: E402
+
+
+class QSerialBusTest(unittest.TestCase):
+ def setUp(self):
+ self.dbc_file = Path(__file__).parent / "test.dbc"
+
+ def test_qfile_open(self):
+ f = QFile(str(self.dbc_file))
+ self.assertTrue(f.open(QFile.OpenModeFlag.ReadOnly), msg=f.errorString())
+ f.close()
+
+ def test_qcandbcfileparser_parse(self):
+ parser = QCanDbcFileParser()
+
+ self.assertTrue(parser.parse([str(self.dbc_file)]), msg=parser.errorString())
+
+ self.assertTrue(parser.parse(str(self.dbc_file)), msg=parser.errorString())
+
+ self.assertTrue(parser.parse([str(self.dbc_file.resolve())]), msg=parser.errorString())
+ self.assertTrue(parser.parse(str(self.dbc_file.resolve())), msg=parser.errorString())
+
+ self.assertFalse(parser.parse(["."]), msg=parser.errorString())
+
+
+if __name__ == '__main__':
+ unittest.main()
--- /dev/null
+"""\
+BO_ 399 STEER_STATUS: 7 EPS
+ SG_ STEER_TORQUE_SENSOR : 7|16@0- (-1,0) [-31000|31000] "tbd" EON
+ SG_ STEER_ANGLE_RATE : 23|16@0- (-0.1,0) [-31000|31000] "deg/s" EON
+ SG_ STEER_STATUS : 39|4@0+ (1,0) [0|15] "" EON
+ SG_ STEER_CONTROL_ACTIVE : 35|1@0+ (1,0) [0|1] "" EON
+ SG_ STEER_CONFIG_INDEX : 43|4@0+ (1,0) [0|15] "" EON
+ SG_ COUNTER : 53|2@0+ (1,0) [0|3] "" EON
+ SG_ CHECKSUM : 51|4@0+ (1,0) [0|15] "" EON
+"""
PYSIDE_TEST(click_test.py)
PYSIDE_TEST(eventfilter_test.py)
-# The test is currently permanently broken, needs to be fixed.
-#PYSIDE_TEST(touchevent_test.py)
+PYSIDE_TEST(touchevent_test.py)
PYSIDE_TEST(qsignalspy_test.py)
PYSIDE_TEST(qvalidator_test.py)
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
from __future__ import annotations
-import gc
import os
import sys
import unittest
from PySide6.QtWidgets import QWidget
from PySide6.QtCore import QPoint, QTimer, Qt, QEvent
-from PySide6.QtGui import QTouchDevice
+from PySide6.QtGui import QPointingDevice
from PySide6.QtTest import QTest
class MyWidget(QWidget):
def __init__(self, parent=None):
- QWidget.__init__(self, parent)
+ super().__init__(parent)
self._sequence = []
- # Fixme (Qt 5): The device needs to be registered (using
- # QWindowSystemInterface::registerTouchDevice()) for the test to work
- self._device = QTouchDevice()
+ self._device = QPointingDevice.primaryPointingDevice()
self.setAttribute(Qt.WA_AcceptTouchEvents)
QTimer.singleShot(200, self.generateEvent)
def event(self, e):
- self._sequence.append(e.type())
- return QWidget.event(self, e)
+ et = e.type()
+ if (et == QEvent.Type.TouchBegin or et == QEvent.Type.TouchUpdate
+ or et == QEvent.Type.TouchEnd):
+ e.accept()
+ self._sequence.append(et)
+ return True
+ return super().event(e)
def generateEvent(self):
- o = QTest.touchEvent(self, self._device)
- o.press(0, QPoint(10, 10))
- o.commit()
- del o
- # PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
- gc.collect()
-
QTest.touchEvent(self, self._device).press(0, QPoint(10, 10))
QTest.touchEvent(self, self._device).stationary(0).press(1, QPoint(40, 10))
QTest.touchEvent(self, self._device).move(0, QPoint(12, 12)).move(1, QPoint(45, 5))
class TouchEventTest(UsesQApplication):
+ @unittest.skipIf(QPointingDevice.primaryPointingDevice() is None, "No device")
def testCreateEvent(self):
w = MyWidget()
w.show()
self.app.exec()
- # same values as C++
- self.assertEqual(w._sequence.count(QEvent.Type.TouchBegin), 2)
+ self.assertEqual(w._sequence.count(QEvent.Type.TouchBegin), 1)
self.assertEqual(w._sequence.count(QEvent.Type.TouchUpdate), 2)
self.assertEqual(w._sequence.count(QEvent.Type.TouchEnd), 1)
PYSIDE_TEST(bug_958.py)
PYSIDE_TEST(bug_965.py)
PYSIDE_TEST(bug_1060.py)
+PYSIDE_TEST(loadUiType_test.py)
PYSIDE_TEST(uiloader_test.py)
PYSIDE_TEST(ui_test.py)
class TestBug913 (unittest.TestCase):
def testIt(self):
- app = QApplication([])
+ app = QApplication([]) # noqa: F841
loader = QUiLoader()
file = Path(__file__).resolve().parent / 'bug_913.ui'
lLoader = QUiLoader()
- # this used to cause a segfault because the old inject code used to destroy the parent layout
+ # this used to cause a segfault because the old inject code used to destroy
+ # the parent layout
file = Path(__file__).resolve().parent / 'bug_958.ui'
assert (file.is_file())
self._cw = lLoader.load(file, self)
from helper.usesqapplication import UsesQApplication
+from PySide6.QtCore import QStandardPaths
from PySide6.QtWidgets import QWidget, QFrame, QPushButton
from PySide6.QtUiTools import loadUiType
class loadUiTypeTester(UsesQApplication):
+
+ @unittest.skipUnless(bool(QStandardPaths.findExecutable("pyside6-uic")), "pyside6-uic missing")
def testFunction(self):
filePath = os.path.join(os.path.dirname(__file__), "minimal.ui")
loaded = loadUiType(filePath)
PYSIDE_TEST(paint_event_test.py)
PYSIDE_TEST(parent_method_test.py)
PYSIDE_TEST(private_mangle_test.py)
+PYSIDE_TEST(pyside_reload_test.py)
PYSIDE_TEST(python_properties_test.py)
PYSIDE_TEST(qabstracttextdocumentlayout_test.py)
PYSIDE_TEST(qaccessible_test.py)
PYSIDE_TEST(qapp_test.py)
PYSIDE_TEST(qapplication_test.py)
PYSIDE_TEST(qapplication_exit_segfault_test.py)
+PYSIDE_TEST(pyside3069.py)
PYSIDE_TEST(qdialog_test.py)
PYSIDE_TEST(qdynamic_signal.py)
-# TODO: This passes, but requires manual button clicking (at least on mac)
-#PYSIDE_TEST(qfontdialog_test.py)
+PYSIDE_TEST(qfontdialog_test.py)
PYSIDE_TEST(qformlayout_test.py)
PYSIDE_TEST(qgraphicsitem_test.py)
PYSIDE_TEST(qgraphicsitem_isblocked_test.py)
PYSIDE_TEST(reference_count_test.py)
PYSIDE_TEST(signature_test.py)
PYSIDE_TEST(standardpixmap_test.py)
-PYSIDE_TEST(test_module_template.py)
PYSIDE_TEST(virtual_protected_inheritance_test.py)
PYSIDE_TEST(virtual_pure_override_test.py)
PYSIDE_TEST(wrong_return_test.py)
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths(False)
-from PySide6.QtWidgets import QMenu, QWidget, QMenuBar, QToolBar
-from helper.usesqapplication import UsesQApplication
+from PySide6.QtWidgets import QMenu, QWidget, QMenuBar, QToolBar # noqa: E402
+from helper.usesqapplication import UsesQApplication # noqa: E402
class TestQActionLifeCycle(UsesQApplication):
w = QWidget()
menu = QMenu(w)
act = menu.addAction("MENU")
- _ref = weakref.ref(act, self.actionDestroyed)
+ _ref = weakref.ref(act, self.actionDestroyed) # noqa: F841
act = None
self.assertFalse(self._actionDestroyed)
menu.clear()
w = QWidget()
menuBar = QMenuBar(w)
act = menuBar.addAction("MENU")
- _ref = weakref.ref(act, self.actionDestroyed)
+ _ref = weakref.ref(act, self.actionDestroyed) # noqa: F841
act = None
# PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
gc.collect()
w = QWidget()
toolBar = QToolBar(w)
act = toolBar.addAction("MENU")
- _ref = weakref.ref(act, self.actionDestroyed)
+ _ref = weakref.ref(act, self.actionDestroyed) # noqa: F841
act = None
# PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
gc.collect()
def testReturnWindow(self):
widget = QWidget()
button = QPushButton(widget)
- self.assertEqual(sys.getrefcount(widget), 2)
+ base_ref_count = sys.getrefcount(widget)
window = button.window()
- self.assertEqual(sys.getrefcount(widget), 3)
- self.assertEqual(sys.getrefcount(window), 3)
+ self.assertEqual(sys.getrefcount(widget), base_ref_count + 1)
+ self.assertEqual(sys.getrefcount(window), base_ref_count + 1)
del widget
# PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
@unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
def testRemoveOrphanWidget(self):
widget = QLabel()
+ base_ref_count = sys.getrefcount(widget)
layout = QHBoxLayout()
layout.addWidget(widget)
- self.assertEqual(sys.getrefcount(widget), 3)
+ self.assertEqual(sys.getrefcount(widget), base_ref_count + 1)
layout.removeWidget(widget)
widget.setObjectName("MyWidget")
- self.assertEqual(sys.getrefcount(widget), 2)
+ self.assertEqual(sys.getrefcount(widget), base_ref_count)
@unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
def testRemoveChildWidget(self):
parent = QLabel()
widget = QLabel(parent)
- self.assertEqual(sys.getrefcount(widget), 3)
+ base_ref_count = sys.getrefcount(widget)
layout = QHBoxLayout()
layout.addWidget(widget)
- self.assertEqual(sys.getrefcount(widget), 3)
+ self.assertEqual(sys.getrefcount(widget), base_ref_count)
layout.removeWidget(widget)
widget.setObjectName("MyWidget")
- self.assertEqual(sys.getrefcount(widget), 3)
+ self.assertEqual(sys.getrefcount(widget), base_ref_count)
if __name__ == "__main__":
a = QApplication([])
w = QWidget()
-l = QGridLayout(w)
+layout = QGridLayout(w)
-l.itemAtPosition(0, 0)
+layout.itemAtPosition(0, 0)
class QAppPresence(unittest.TestCase):
def testBug(self):
- app = QApplication(sys.argv)
+ app = QApplication(sys.argv) # noqa: F841
window = QMainWindow()
- l = window.layout()
- self.assertTrue(isinstance(l, QLayout))
+ layout = window.layout()
+ self.assertTrue(isinstance(layout, QLayout))
if __name__ == '__main__':
class BugTest(unittest.TestCase):
def test(self):
- app = QApplication(sys.argv)
+ app = QApplication(sys.argv) # noqa: F841
scene = QGraphicsScene()
item = DiagramItem()
item2 = DiagramItem()
t = Test()
t.show()
QTimer.singleShot(0, t.close)
-sys.exit(a.exec_())
+sys.exit(a.exec())
pass
def testConnectSignal(self):
- app = QApplication([])
+ app = QApplication([]) # noqa: F841
m2 = M2()
# Test if the aboutToShow signal was translated to correct type
m2.aboutToShow.connect(self.aboutToShowHandler)
"""Test to check a crash at exit"""
def testIt(self):
- app = QApplication([])
+ app = QApplication([]) # noqa: F841
textEdit = QPlainTextEdit()
completer = QCompleter(("foo", "bar"), textEdit)
completer.setWidget(textEdit)
class Bug576(unittest.TestCase):
def onButtonDestroyed(self, button):
self._destroyed = True
- self.assertTrue(isinstance(button, QPushButton))
@unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
def testWidgetParent(self):
b = QPushButton("test")
b.destroyed[QObject].connect(self.onButtonDestroyed)
- self.assertEqual(sys.getrefcount(b), 2)
+ base_ref_count = sys.getrefcount(b)
b.setParent(w)
- self.assertEqual(sys.getrefcount(b), 3)
+ self.assertEqual(sys.getrefcount(b), base_ref_count + 1)
b.parent()
- self.assertEqual(sys.getrefcount(b), 3)
+ self.assertEqual(sys.getrefcount(b), base_ref_count + 1)
b.setParent(None)
- self.assertEqual(sys.getrefcount(b), 2)
+ self.assertEqual(sys.getrefcount(b), base_ref_count)
del b
# PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
gc.collect()
class Bug640(unittest.TestCase):
+ """crash!?"""
def testIt(self):
option = QStyleOptionGraphicsItem()
- a = option.state # crash!?
+ a = option.state # noqa: F841
if __name__ == "__main__":
class TestBug653(unittest.TestCase):
"""Crash after calling QWizardPage.wizard()"""
def testIt(self):
- app = QApplication([])
+ app = QApplication([]) # noqa: F841
wizard = QWizard()
page = QWizardPage()
v.setModel(a)
self.setCentralWidget(v)
# Test index() method (see PYSIDE-570, PYSIDE-331)
- index = a.index(0, 0, QModelIndex())
+ index = a.index(0, 0, QModelIndex()) # noqa: F841
app = QApplication([])
scene = QGraphicsScene()
hello = scene.addText("Hello")
+ base_ref_count = sys.getrefcount(hello)
scene.addText("World")
- self.assertEqual(sys.getrefcount(hello), 3)
scene.clear()
- self.assertEqual(sys.getrefcount(hello), 2)
+ self.assertEqual(sys.getrefcount(hello), base_ref_count - 1)
self.assertEqual(len(scene.items()), 0)
self.assertRaises(RuntimeError, hello.isVisible) # the C++ object was deleted
class TestBug693(unittest.TestCase):
def testIt(self):
- app = QApplication([])
+ app = QApplication([]) # noqa: F841
model = MyModel()
view = QListView()
view.setModel(model)
from helper.usesqapplication import UsesQApplication
-from PySide6.QtCore import QTimer
+from PySide6.QtCore import QCoreApplication, QTimer
from PySide6.QtGui import QPainter
from PySide6.QtWidgets import QWidget
class MyWidget(QWidget):
+ def __init__(self):
+ super().__init__()
+ self._info = None
+
def paintEvent(self, e):
- p = QPainter(self)
- self._info = p.fontInfo()
- self._app.quit()
+ with QPainter(self) as p:
+ self._info = p.fontInfo()
+ QTimer.singleShot(0, qApp.quit) # noqa: F821
class TestQPainter(UsesQApplication):
def testFontInfo(self):
w = MyWidget()
- w._app = self.app
- w._info = None
- QTimer.singleShot(300, w.show)
+ w.show()
+ while not w.windowHandle().isExposed():
+ QCoreApplication.processEvents()
self.app.exec()
self.assertTrue(w._info)
def testIt(self):
- app = QApplication([])
+ app = QApplication([]) # noqa: F841
self.assertEqual("<__main__.MyQObject(0x", repr(MyQObject())[:22])
self.assertEqual("<__main__.MyQWidget(0x", repr(MyQWidget())[:22])
self.paintReceived.emit()
def paintEvent(self, e):
- p = QPainter(self)
- style = QApplication.style()
- option = QStyleOptionButton()
- style.drawControl(QStyle.ControlElement.CE_PushButton, option, p)
+ with QPainter(self) as p:
+ style = QApplication.style()
+ option = QStyleOptionButton()
+ self.initStyleOption(option)
+ style.drawControl(QStyle.ControlElement.CE_PushButton, option, p)
self._painted = True
QTimer.singleShot(0, self._emitPainted)
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QApplication, QHeaderView
+received_column = None
+received_order = None
-def foo(a, b):
- pass
+
+def foo(colum, order):
+ global received_column, received_order
+ received_column = colum
+ received_order = order
class TestBug941 (unittest.TestCase):
view = QHeaderView(Qt.Orientation.Horizontal)
self.assertTrue(view.sortIndicatorChanged.connect(foo))
# this can't raise an exception!
- view.sortIndicatorChanged.emit(0, Qt.Orientation.Vertical)
+ view.sortIndicatorChanged.emit(0, Qt.SortOrder.DescendingOrder)
+ self.assertEqual(received_column, 0)
+ self.assertEqual(received_order, Qt.SortOrder.DescendingOrder)
if __name__ == '__main__':
def testIt(self):
self.arg = None
- app = QApplication([])
+ app = QApplication([]) # noqa: F841
obj = QComboBox()
obj.currentIndexChanged.connect(self.callback)
obj.currentIndexChanged.emit(5)
def testIt(self):
self.arg = None
- app = QApplication([])
+ app = QApplication([]) # noqa: F841
obj = QTabWidget()
obj.currentChanged.connect(self.callback)
obj.currentChanged.emit(5)
class TestBug998 (unittest.TestCase):
def testNoFocusWindow(self):
widget = QApplication.focusWidget()
- self.assertTrue(widget == None)
+ self.assertTrue(widget is None)
if __name__ == '__main__':
--- /dev/null
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+import os
+import sys
+import unittest
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths # noqa: E402
+init_test_paths(False)
+
+from PySide6.QtCore import Qt # noqa: E402
+from PySide6.QtWidgets import QApplication, QComboBox, QGraphicsScene, QGraphicsView # noqa: E402
+
+from helper.usesqapplication import UsesQApplication # noqa: E402
+
+
+class BugTest(UsesQApplication):
+ """PYSIDE-3069: Test that the conversion of an element of a list
+ QGraphicsItem* to QGraphicsProxyWidget* (inheriting QObject/QGraphicsItem)
+ works correctly without crash.
+
+ For this, we need a QGraphicsProxyWidget for which no wrapper exists,
+ created in C++. So, we populate a combo, add it to the scene and show its
+ popup, which creates a top level that is automatically wrapped by
+ another QGraphicsProxyWidget. This, we print from the list of items().
+
+ See also PYSIDE-86, PYSIDE-1887."""
+ def test(self):
+ qApp.setEffectEnabled(Qt.UI_AnimateCombo, False) # noqa: F821
+ cb = QComboBox()
+ cb.addItem("i1")
+ cb.addItem("i2")
+ scene = QGraphicsScene()
+ scene.addWidget(cb)
+ view = QGraphicsView(scene)
+ view.show()
+ cb.showPopup()
+ while not view.windowHandle().isExposed():
+ QApplication.processEvents()
+ items = scene.items()
+ self.assertEqual(len(items), 2) # Combo and its popup, created in C++
+ for i in items:
+ print(i)
+ view.close()
+
+
+if __name__ == "__main__":
+ unittest.main()
--- /dev/null
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+from __future__ import annotations
+
+import importlib
+import importlib.util
+import os
+import sys
+import unittest
+
+from tempfile import TemporaryDirectory
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths
+init_test_paths(False)
+
+
+def reload_module(moduleName):
+ importlib.reload(moduleName)
+
+
+class TestModuleReloading(unittest.TestCase):
+ def setUp(self):
+ orig_path = Path(__file__).resolve().parent
+ self._src = orig_path / 'test_module_template.py'
+ self._workdir = TemporaryDirectory()
+ self._dst = Path(self._workdir.name) / 'test_module.py'
+ self._dst.write_bytes(self._src.read_bytes())
+ sys.path.append(self._workdir.name)
+
+ def tearDown(self):
+ sys.path.remove(self._workdir.name)
+ self._workdir = None
+
+ def _increment_module_value(self):
+ with self._dst.open(mode='a') as modfile:
+ modfile.write('Sentinel.value += 1\n')
+ if not sys.dont_write_bytecode:
+ import importlib.util
+ cacheFile = importlib.util.cache_from_source(os.fspath(self._dst))
+ os.remove(cacheFile)
+
+ def testModuleReloading(self):
+ '''Test module reloading with on-the-fly modifications.'''
+
+ import test_module
+ self.assertEqual(test_module.Sentinel.value, 10)
+
+ self._increment_module_value()
+ reload_module(sys.modules['test_module'])
+ self.assertEqual(test_module.Sentinel.value, 11)
+
+ reload_module(sys.modules['test_module'])
+ self.assertEqual(test_module.Sentinel.value, 11)
+
+ self._increment_module_value()
+ reload_module(sys.modules['test_module'])
+ self.assertEqual(test_module.Sentinel.value, 12)
+
+
+if __name__ == "__main__":
+ unittest.main()
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
sys.path.append(os.fspath(Path(__file__).resolve().parents[1] / "util"))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths()
-from PySide6.QtCore import QTimer
-from PySide6.QtWidgets import QApplication
+from PySide6.QtCore import QTimer # noqa: E402
+from PySide6.QtWidgets import QApplication # noqa: E402
app_instance = QApplication([])
# If the following line is commented, application doesn't crash on exit anymore.
app_instance2 = app_instance
-QTimer.singleShot(0, qApp.quit)
-app_instance.exec_()
+QTimer.singleShot(0, qApp.quit) # noqa: F821
+app_instance.exec()
def testQApp(self):
# QtGui.qApp variable is instance of QApplication
- self.assertTrue(isinstance(qApp, QApplication))
+ self.assertTrue(isinstance(qApp, QApplication)) # noqa: F821
def main():
- app = QApplication([])
+ app = QApplication([]) # noqa: F841
unittest.main()
init_test_paths(False)
from PySide6.QtCore import Slot, QTimer
-from PySide6.QtWidgets import QDialog, QMainWindow
+from PySide6.QtWidgets import QApplication, QDialog, QMainWindow
from helper.timedqapplication import TimedQApplication
+def is_exposed(widget):
+ result = False
+ if widget.isVisible():
+ handle = widget.windowHandle()
+ if handle:
+ result = handle.isExposed()
+ return result
+
+
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Main")
self.dialog = None
+ self._timer = QTimer()
+ self._timer.setInterval(50)
+ self._timer.timeout.connect(self._timer_handler)
+ self._timer.start()
@Slot()
def execDialog(self):
dialog.exec()
self.close()
+ @Slot()
+ def _timer_handler(self):
+ """Periodically check for the dialog to appear and close it."""
+ for widget in QApplication.topLevelWidgets():
+ if isinstance(widget, QDialog) and is_exposed(widget):
+ widget.reject()
+
class DialogExecTest(TimedQApplication):
"""Test whether the parent-child relationship (dialog/main window) is removed when
def testExec(self):
self._window.show()
- QTimer.singleShot(500, self._window.execDialog)
+ while not is_exposed(self._window):
+ QApplication.processEvents()
+ QTimer.singleShot(0, self._window.execDialog)
self.app.exec()
self.assertTrue(self._window.dialog() is None)
from init_paths import init_test_paths
init_test_paths(False)
+from PySide6.QtCore import QTimer
from PySide6.QtGui import QFont
-from PySide6.QtWidgets import QFontDialog
+from PySide6.QtWidgets import QApplication, QDialog, QFontDialog
from helper.timedqapplication import TimedQApplication
+def is_exposed(widget):
+ result = False
+ if widget.isVisible():
+ handle = widget.windowHandle()
+ if handle:
+ result = handle.isExposed()
+ return result
+
+
class TestFontDialog(TimedQApplication):
- def testGetFont(self):
- QFontDialog.getFont()
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self._timer = None
+
+ def setUp(self, timeout=100):
+ super().setUp(timeout)
+ if not self._timer:
+ self._timer = QTimer()
+ self._timer.setInterval(50)
+ self._timer.timeout.connect(self._timer_handler)
+ self._timer.start()
- def testGetFontQDialog(self):
- QFontDialog.getFont(QFont("FreeSans", 10))
+ def _timer_handler(self):
+ """Periodically check for the dialog to appear and close it."""
+ for widget in QApplication.topLevelWidgets():
+ if isinstance(widget, QDialog) and is_exposed(widget):
+ widget.accept()
def testGetFontQDialogQString(self):
- QFontDialog.getFont(QFont("FreeSans", 10), None, "Select font")
+ r = QFontDialog.getFont(QFont("FreeSans", 10), None, "Select font",
+ QFontDialog.FontDialogOption.DontUseNativeDialog)
+ self.assertTrue(type(r) is tuple)
+ self.assertEqual(len(r), 2)
+ self.assertTrue(r[0])
+ self.assertTrue(type(r[1]) is QFont)
if __name__ == '__main__':
# and then the QVariant was not associated with
# a QGraphicsItem but a QObjectItem because the base
# class was a QObject.
+ # See also PYSIDE-1887, PYSIDE-3069
gobjA = GObjA()
gobjA.setParentItem(w)
+ print(gobjA.parentItem())
self.assertIs(type(w), type(gobjA.parentItem()))
+ # PYSIDE-3115: QVariant conversion of the parent
+ # (Python class inheriting QGraphicsObject).
+ parentA = GObjA()
+ gobjA = GObjA()
+ gobjA.setParentItem(parentA)
+ self.assertIs(type(parentA), type(gobjA.parentItem()))
+
gobjB = GObjB()
gobjB.setParentItem(w)
self.assertIs(type(w), type(gobjB.parentItem()))
from helper.usesqapplication import UsesQApplication
-def close_dialog():
- for w in QApplication.topLevelWidgets():
- if isinstance(w, QDialog):
- w.reject()
+def is_exposed(widget):
+ result = False
+ if widget.isVisible():
+ handle = widget.windowHandle()
+ if handle:
+ result = handle.isExposed()
+ return result
class TestInputDialog(UsesQApplication):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self._timer = None
+
+ def setUp(self):
+ super().setUp()
+ if not self._timer:
+ self._timer = QTimer()
+ self._timer.setInterval(50)
+ self._timer.timeout.connect(self._timer_handler)
+ self._timer.start()
+
+ def _timer_handler(self):
+ """Periodically check for the dialog to appear and close it."""
+ for widget in QApplication.topLevelWidgets():
+ if isinstance(widget, QDialog) and is_exposed(widget):
+ widget.reject()
+
def testGetDouble(self):
- QTimer.singleShot(500, close_dialog)
self.assertEqual(QInputDialog.getDouble(None, "title", "label"), (0.0, False))
def testGetInt(self):
- QTimer.singleShot(500, close_dialog)
self.assertEqual(QInputDialog.getInt(None, "title", "label"), (0, False))
def testGetItem(self):
- QTimer.singleShot(500, close_dialog)
(item, bool) = QInputDialog.getItem(None, "title", "label", ["1", "2", "3"])
self.assertEqual(str(item), "1")
def testGetText(self):
- QTimer.singleShot(500, close_dialog)
(text, bool) = QInputDialog.getText(None, "title", "label")
self.assertEqual(str(text), "")
topLayout = QGridLayout()
# unique reference
- self.assertEqual(sys.getrefcount(w), 2)
- self.assertEqual(sys.getrefcount(ow), 2)
+ base_ref_count_w = sys.getrefcount(w)
+ base_ref_count_ow = sys.getrefcount(ow)
topLayout.addWidget(w, 0, 0)
topLayout.addWidget(ow, 1, 0)
# layout keep the referemce
- self.assertEqual(sys.getrefcount(w), 3)
- self.assertEqual(sys.getrefcount(ow), 3)
+ self.assertEqual(sys.getrefcount(w), base_ref_count_w + 1)
+ self.assertEqual(sys.getrefcount(ow), base_ref_count_ow + 1)
mainLayout = QGridLayout()
mainLayout.addLayout(topLayout, 1, 0, 1, 4)
# the same reference
- self.assertEqual(sys.getrefcount(w), 3)
- self.assertEqual(sys.getrefcount(ow), 3)
+ self.assertEqual(sys.getrefcount(w), base_ref_count_w + 1)
+ self.assertEqual(sys.getrefcount(ow), base_ref_count_ow + 1)
mw.setLayout(mainLayout)
# now trasfer the ownership to mw
- self.assertEqual(sys.getrefcount(w), 3)
- self.assertEqual(sys.getrefcount(ow), 3)
+ self.assertEqual(sys.getrefcount(w), base_ref_count_w + 1)
+ self.assertEqual(sys.getrefcount(ow), base_ref_count_ow + 1)
del mw
# remove the ref and invalidate the widget
- self.assertEqual(sys.getrefcount(w), 2)
- self.assertEqual(sys.getrefcount(ow), 2)
+ self.assertEqual(sys.getrefcount(w), base_ref_count_w)
+ self.assertEqual(sys.getrefcount(ow), base_ref_count_ow)
if __name__ == '__main__':
@unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
def testOwnershipTransfer(self):
b = QPushButton("teste")
+ base_ref_count = sys.getrefcount(b)
layout = MyLayout()
layout.addWidget(b)
- self.assertEqual(sys.getrefcount(b), 2)
+ self.assertEqual(sys.getrefcount(b), base_ref_count)
w = QWidget()
# transfer ref
w.setLayout(layout)
- self.assertEqual(sys.getrefcount(b), 3)
+ self.assertEqual(sys.getrefcount(b), base_ref_count + 1)
@unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
def testReferenceTransfer(self):
b = QPushButton("teste")
+ base_ref_count = sys.getrefcount(b)
layout = QHBoxLayout()
# keep ref
layout.addWidget(b)
- self.assertEqual(sys.getrefcount(b), 3)
+ self.assertEqual(sys.getrefcount(b), base_ref_count + 1)
w = QWidget()
# transfer ref
w.setLayout(layout)
- self.assertEqual(sys.getrefcount(b), 3)
+ self.assertEqual(sys.getrefcount(b), base_ref_count + 1)
# release ref
del w
- self.assertEqual(sys.getrefcount(b), 2)
+ self.assertEqual(sys.getrefcount(b), base_ref_count)
def testMissingFunctions(self):
w = QWidget()
def testConstructorWithNone(self):
# Bug 452 - QListWidgetItem() not casting NoneType to null correctly.
- item = QListWidgetItem(None, 123)
+ item = QListWidgetItem(None, 123) # noqa: F841
if __name__ == '__main__':
from init_paths import init_test_paths
init_test_paths(False)
-from PySide6.QtCore import QTimer
from PySide6.QtWidgets import QMainWindow, QPushButton, QToolButton, QWidget
from helper.usesqapplication import UsesQApplication
class TestMainWindow(UsesQApplication):
- def testCreateToolbar(self):
- w = MainWindow()
- w.show()
- QTimer.singleShot(1000, self.app.quit)
- self.app.exec()
-
def objDel(self, obj):
self.app.quit()
def testRefCountToNull(self):
w = QMainWindow()
c = QWidget()
- self.assertEqual(sys.getrefcount(c), 2)
+ base_ref_count = sys.getrefcount(c)
w.setCentralWidget(c)
- self.assertEqual(sys.getrefcount(c), 3)
+ self.assertEqual(sys.getrefcount(c), base_ref_count + 1)
wr = weakref.ref(c, self.objDel) # noqa: F841
w.setCentralWidget(None)
c = None
def testRefCountToAnother(self):
w = QMainWindow()
c = QWidget()
- self.assertEqual(sys.getrefcount(c), 2)
+ base_ref_count = sys.getrefcount(c)
w.setCentralWidget(c)
- self.assertEqual(sys.getrefcount(c), 3)
+ self.assertEqual(sys.getrefcount(c), base_ref_count + 1)
c2 = QWidget()
+ base_ref_count = sys.getrefcount(c2)
w.setCentralWidget(c2)
- self.assertEqual(sys.getrefcount(c2), 3)
+ self.assertEqual(sys.getrefcount(c2), base_ref_count + 1)
wr = weakref.ref(c, self.objDel) # noqa: F841
w.setCentralWidget(None)
def testAddActionWithoutKeySequenceCallable(self):
# bug #280
- action = self.menu.addAction(self.app.tr('aaa'), lambda: 1)
+ action = self.menu.addAction(self.app.tr('aaa'), lambda: 1) # noqa: F841
def testAddActionKeySequenceCallable(self):
# bug #228
- action = self.menu.addAction(self.app.tr('aaa'), lambda: 1,
+ action = self.menu.addAction(self.app.tr('aaa'), lambda: 1, # noqa: F841
QKeySequence(self.app.tr('Ctrl+O')))
def testAddActionKeySequenceSlot(self):
- action = self.menu.addAction('Quit', self.app, SLOT('quit()'),
- QKeySequence('Ctrl+O'))
+ action = self.menu.addAction('Quit', QKeySequence('Ctrl+O'), # noqa: F841
+ self.app, SLOT('quit()'))
class QMenuAddActionWithIcon(UsesQApplication):
def testAddActionWithoutKeySequenceCallable(self):
# bug #280
- action = self.menu.addAction(self.icon, self.app.tr('aaa'), lambda: 1)
+ action = self.menu.addAction(self.icon, self.app.tr('aaa'), lambda: 1) # noqa: F841
def testAddActionKeySequenceCallable(self):
# bug #228
- action = self.menu.addAction(self.icon, self.app.tr('aaa'), lambda: 1,
- QKeySequence(self.app.tr('Ctrl+O')))
+ action = self.menu.addAction(self.icon, self.app.tr('aaa'), lambda: 1, # noqa: F841
+ QKeySequence(self.app.tr('Ctrl+O'))) # noqa: F841
def testAddActionKeySequenceSlot(self):
- action = self.menu.addAction(self.icon, 'Quit', self.app, SLOT('quit()'),
- QKeySequence('Ctrl+O'))
+ action = self.menu.addAction(self.icon, 'Quit', QKeySequence('Ctrl+O'), # noqa: F841
+ self.app, SLOT('quit()')) # noqa: F841
if __name__ == '__main__':
init_test_paths(False)
from helper.usesqapplication import UsesQApplication
-from PySide6.QtCore import QTimer
+from PySide6.QtCore import QCoreApplication, QTimer
from PySide6.QtGui import QPicture, QPainter
from PySide6.QtWidgets import QWidget
class MyWidget(QWidget):
+ def __init__(self, picture):
+ super().__init__()
+ self._picture = picture
+
def paintEvent(self, e):
with QPainter(self) as p:
p.drawPicture(0, 0, self._picture)
- self._app.quit()
+ QTimer.singleShot(0, qApp.quit) # noqa: F821
class QPictureTest(UsesQApplication):
self.assertEqual(picture2.data(), picture.data())
- w = MyWidget()
- w._picture = picture2
- w._app = self.app
+ w = MyWidget(picture2)
- QTimer.singleShot(300, w.show)
+ w.show()
+ while not w.windowHandle().isExposed():
+ QCoreApplication.processEvents()
self.app.exec()
@unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
def testSetStyleOwnership(self):
style = QStyleFactory.create(QStyleFactory.keys()[0])
- self.assertEqual(sys.getrefcount(style), 2)
+ base_ref_count = sys.getrefcount(style)
QApplication.instance().setStyle(style)
- self.assertEqual(sys.getrefcount(style), 3)
+ self.assertEqual(sys.getrefcount(style), base_ref_count + 1)
if __name__ == '__main__':
# C++ object already deleted." when accessing the QTextTable
del self.cursor
self.assertEqual(sys.getrefcount(self.table), 2)
- cell = self.table.cellAt(0, 0)
+ cell = self.table.cellAt(0, 0) # noqa: F841
if __name__ == "__main__":
treeWidget.insertTopLevelItem(0, item)
# Adding QPushButton inside the method
treeWidget.setItemWidget(item, 1,
- QPushButton('Push button on column 1'))
+ QPushButton('Push button on column 1'))
# Getting the widget back
w = treeWidget.itemWidget(treeWidget.itemAt(0, 1), 1)
SRC_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(SRC_DIR)),
'shiboken6', 'tests'))
-from shiboken_paths import (get_dir_env_var, get_build_dir, add_python_dirs,
- add_lib_dirs, shiboken_paths)
+from shiboken_paths import (get_dir_env_var, get_build_dir, add_python_dirs, # noqa: E402
+ add_lib_dirs, shiboken_paths) # noqa: E402
def _get_qt_dir():
--python <python> use that specific python interpreter
--dry-run try it first without compilation
--pip automatically install the needed modules
+ --absolute compare against the status from
+ b887919ea244a057f15be9c1cdc652538e3fe9a0
+ Yocto: allow LLVM 14 for building PySide
+ 2025-01-23 18:18
+ --limited-api yes|no default=yes
"""
import argparse
+import getpass
import os
import platform
import re
from ast import literal_eval
from pathlib import Path
-defaults = {
- "Darwin": "/Users/tismer/.pyenv/versions/3.12.5/bin/python3",
- "Windows": "d:/py312_64/python.exe",
- "Linux": "/home/ctismer/.pyenv/versions/3.12.5/bin/python3",
+
+defaults = { # Python, extras
+ "Darwin": ("/Users/tismer/.pyenv/versions/3.12.5/bin/python3", []), # noqa: E241
+ "Windows": ("d:/py312_64/python.exe", []), # noqa: E241
+ "Linux": ("/home/ctismer/.pyenv/versions/3.12.5/bin/python3", []) # noqa: E241
+}
+
+reference = { # Limited API no / yes
+ "Darwin": (26165741, 26078531), # noqa: E241
+ "Windows": (15324160, 15631872), # noqa: E241
+ "Linux": (19203176, 19321976), # noqa: E241
}
+if "tismer" not in getpass.getuser():
+ # assume a colleague.
+ defaults["Linux"] = "python", ["--qt-src-dir", "/~/qt-69/qt-69/qtbase"]
+ # defaults["Windows"] = "...", [...] # please insert your defaults
+
def setup_project_dir():
look_for = Path("testing")
while here / look_for not in here.iterdir():
parent = here.parent
if parent == here:
- raise SystemError(look_for + " not found!")
+ raise SystemError(f"{look_for} not found!")
here = parent
fsp = os.fspath(here)
if fsp not in sys.path:
from build_scripts.main import config
pattern = r'Programming Language :: Python :: (\d+)\.(\d+)'
hist = []
- for line in config.python_version_classifiers:
+ for line in config.classifiers:
found = re.search(pattern, line)
if found:
ma = int(found.group(1))
setup_project_dir()
plat = platform.system()
-options = [
- "setup.py", "build", "--limited-api=no", "--skip-docs", "--no-qt-tools",
- "--module-subset=Core,Gui,Widgets"]
-
-options_base = options + ["--unoptimize=all"]
-options_best = options
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--dry-run", "-d", action="store_true")
parser.add_argument("--pip", action="store_true", help="""
Install the necessary modules automatically, which can save some trouble""")
- args = parser.parse_args()
+ parser.add_argument("--absolute", "-a", action="store_true", help="""
+ Measure against the state on 2025-01-23""")
+ parser.add_argument("--limited-api", "-l", choices=["yes", "no"], default="yes", help="""
+ Use of limited API. Recommended because this is the CI default""")
- python = args.python or defaults[plat] if plat in defaults else args.python
+ args = parser.parse_args()
+ python = args.python or defaults[plat][0] if plat in defaults else args.python
python = Path(python).expanduser()
if not python.exists:
subprocess.run([python, "-m", "pip", "uninstall", "-y"] + needs_imports)
subprocess.run([python, "-m", "pip", "install"] + needs_imports)
+ options = [
+ "setup.py", "build", "--limited-api=" + args.limited_api, "--skip-docs",
+ "--log-level", "quiet", "--unity", "--no-qt-tools",
+ "--module-subset=Core,Gui,Widgets"] + defaults[plat][1]
+ options_base = options + ["--unoptimize=all"]
+ options_best = options
+
+ use_limited_api = args.limited_api == "yes"
skip = args.dry_run
- cmd = [python] + options_base
- if not skip:
- subprocess.run(cmd)
+ if args.absolute:
+ res_base = reference[plat][use_limited_api]
+ else:
+ cmd = [python] + options_base
+ if not skip:
+ subprocess.run(cmd)
- build_dir = get_build_dir()
- res_base = get_result_size(build_dir)
+ build_dir = get_build_dir()
+ res_base = get_result_size(build_dir)
cmd = [python] + options_best
if not skip:
build_dir = get_build_dir()
res_best = get_result_size(build_dir)
+ add_text = " on 2025-01-27" if args.absolute else ""
print()
print(f"Compiling with {python}")
- print(f"Platform = {plat}")
- print(f"base size = {res_base}")
+ print(f"Platform = {plat} limited_api = {args.limited_api}")
+ print(f"base size = {res_base}{add_text}")
print(f"best size = {res_best}")
print(f"improvement {(res_base - res_best) / res_base:%}")
if skip:
PYSIDE_TEST(notify_id.py)
PYSIDE_TEST(properties_test.py)
PYSIDE_TEST(property_python_test.py)
+PYSIDE_TEST(snake_case_sub.py)
PYSIDE_TEST(snake_case_test.py)
PYSIDE_TEST(true_property_test.py)
PYSIDE_TEST(qapp_like_a_macro_test.py)
from init_paths import init_test_paths
init_test_paths(True)
-import shiboken6
from testbinding import getHiddenObject
# PYSIDE-1735: We are testing that opcodes do what they are supposed to do.
-# This is needed in the PyEnum forgiveness mode where we need
-# to introspect the code if an Enum was called with no args.
+# This is needed in the PyEnum forgiveness mode (ENOPT_NO_ZERODEFAULT)
+# where we need to introspect the code if an Enum was called with no args,
+# enabling default construction like 'f = Qt.WindowFlags()'.
+# Adapt for each Python version by checking the defines in the generated header opcode_ids.h
+# egrep '( RESUME | LOAD_GLOBAL | LOAD_ATTR | PUSH_NULL | CALL | STORE_FAST | RETURN_CONST )' opcode_ids.h
+# See also sbkfeature_base.cpp
# flake8: noqa
class InvestigateOpcodesTest(unittest.TestCase):
_sin = sys.implementation.name
- @unittest.skipIf(hasattr(sys.flags, "nogil"), f"{_sin} has different opcodes")
def testByteCode(self):
import dis
# opname, opcode, arg
('STORE_FAST', 125, 1),
('RETURN_CONST', 121, 0)]
- if sys.version_info[:2] >= (3, 13):
+ if sys.version_info[:2] == (3, 13):
result_1 = [('RESUME', 149, 0),
('LOAD_GLOBAL', 91, 0),
('STORE_FAST', 110, 1),
('RETURN_CONST', 103, 0)]
+ if sys.version_info[:2] >= (3, 14):
+
+ result_1 = [('RESUME', 128, 0),
+ ('LOAD_GLOBAL', 92, 0),
+ ('LOAD_ATTR', 80, 2),
+ ('STORE_FAST', 112, 1),
+ ('LOAD_CONST', 82, 0),
+ ('RETURN_VALUE', 35, None)
+ ]
+
+ result_2 = [('RESUME', 128, 0),
+ ('LOAD_GLOBAL', 92, 0),
+ ('LOAD_ATTR', 80, 2),
+ ('PUSH_NULL', 33, None),
+ ('CALL', 52, 0),
+ ('STORE_FAST', 112, 1),
+ ('LOAD_CONST', 82, 0),
+ ('RETURN_VALUE', 35, None)]
+
self.assertEqual(self.read_code(self.probe_function1), result_1)
self.assertEqual(self.read_code(self.probe_function2), result_2)
testfunc = QtCore.QUrl.fromStringList
# use a generator (iterable)
self.assertEqual(testfunc(gen(["asd", "ghj"])),
- [PySide6.QtCore.QUrl('asd'), PySide6.QtCore.QUrl('ghj')])
+ [PySide6.QtCore.QUrl('asd'), PySide6.QtCore.QUrl('ghj')])
# use an iterator
self.assertEqual(testfunc(iter(["asd", "ghj"])),
- [PySide6.QtCore.QUrl('asd'), PySide6.QtCore.QUrl('ghj')])
+ [PySide6.QtCore.QUrl('asd'), PySide6.QtCore.QUrl('ghj')])
self.assertRaises(IndexError, testfunc, gen(["asd", "crash", "ghj"]))
# testing QMatrix4x4
testfunc = QtGui.QMatrix4x4
from __future__ import annotations
import os
+import re
import sys
import unittest
import subprocess
USE_MYPY = True if is_ci else HAVE_MYPY
+def dump_erroneous_pyi_files(err_lines, pyi_dir):
+ seen = set()
+ for err_line in err_lines:
+ if match := re.search(r"Qt\w+\.pyi", err_line):
+ if (pyi := match.group(0)) not in seen:
+ seen.add(pyi)
+ print(f"----- dump of {pyi} -----")
+ with open(Path(pyi_dir) / pyi) as f:
+ line_no = 0
+ for line in f:
+ line_no += 1
+ print(f"{pyi}:{line_no} {line.rstrip()}")
+ print()
+
+
@unittest.skipIf(not USE_MYPY, "The mypy test was skipped because mypy is not installed")
@unittest.skipIf(SKIP_MYPY_TEST, "The mypy test was disabled")
class MypyCorrectnessTest(unittest.TestCase):
def testMypy(self):
self.assertTrue(HAVE_MYPY)
insert_version = ["--python-version", "3.11"] if sys.version_info[:2] < (3, 11) else []
- cmd = ([sys.executable, "-m", "mypy", "--cache-dir", self.cache_dir]
+ cmd = ([sys.executable, "-m", "mypy", "--pretty", "--cache-dir", self.cache_dir]
+ insert_version + [self.pyside_dir])
time_pre = time.time()
ret = subprocess.run(cmd, capture_output=True)
time_post = time.time()
- for line in ret.stdout.decode("utf-8").split("\n"):
+ err_lines = ret.stdout.decode("utf-8").split("\n")
+ for line in err_lines:
print(line)
print(f"Time used for mypy test = {(time_post - time_pre):.5} s")
+ if ret.returncode != 0 and os.environ.get("QTEST_ENVIRONMENT", "") == "ci":
+ dump_erroneous_pyi_files(err_lines, self.pyside_dir)
self.assertEqual(ret.returncode, 0)
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths(False)
-from PySide6.QtCore import QObject, Signal, Property, Slot
+from PySide6.QtCore import QObject, Signal, Property, Slot # noqa: E402
'''Tests that the signal notify id of a property is correct, aka corresponds to the initially set
notify method.'''
from pathlib import Path
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
-from init_paths import init_test_paths
+from init_paths import init_test_paths # noqa: E402
init_test_paths(False)
-import PySide6.QtCore as QtCore
+import PySide6.QtCore as QtCore # noqa: E402
class Whatever(QtCore.QObject):
def f_(f):
def f__(self):
result = f(self)
- s = signal.__get__(self)
+ s = signal.__get__(self) # noqa: F841
print(result)
return result
return f__
def __init__(self, p=None):
super().__init__(p)
self.received_handle = -1
+ self.received_name = ''
+
+ @Slot(QObject)
+ def slotObjectByConstRef(self, o):
+ self.received_name = o.objectName()
@Slot(Connection)
def connectionSlot(self, c):
def setUp(self):
self.obj1 = TestObject(0)
+ self.obj1.setObjectName('obj1')
self.obj2 = TestObject(0)
+ self.obj2.setObjectName('obj2')
self.one_called = 0
self.two_called = 0
self.assertEqual(self.one_called, 1)
self.assertEqual(self.two_called, 2)
+ def testSignalObjectByConstRef(self):
+ receiver = Receiver()
+ self.obj1.objectByConstRef.connect(receiver.slotObjectByConstRef)
+ self.obj1.emitObjectByConstRef()
+ self.assertEqual(receiver.received_name, 'obj1')
+
if __name__ == '__main__':
unittest.main()
emit flagsSignal(alignment);
}
+void TestObject::emitObjectByConstRef()
+{
+ emit objectByConstRef(*this);
+}
+
void TestObject::setQLatin1String(QLatin1String v)
{
m_qLatin1String = v;
void emitFlagsSignal(Qt::Alignment alignment);
+ void emitObjectByConstRef();
+
static constexpr auto LATIN1_TEST_FIELD = QLatin1StringView("test");
void setQLatin1String(QLatin1String v);
void signalWithTypedefValue(TypedefValue value);
void signalWithContainerTypedefValue(const IntList &il);
void flagsSignal(Qt::Alignment alignment);
+ void objectByConstRef(const QObject &qobject);
private:
int m_idValue;
from util import (isolate_warnings, check_warnings, suppress_warnings, warn,
is_ci, qt_version, get_script_dir, get_effective_refpath,
get_refpath, import_refmodule)
-from PySide6 import *
+from PySide6 import * # noqa
refPath = get_refpath()
effectiveRefPath = get_effective_refpath()
if refPath != effectiveRefPath:
print("*** Falling back to ", effectiveRefPath, " since expected ",
- refPath, " does not exist")
+ refPath, " does not exist")
script_dir = get_script_dir()
shortpath = os.path.relpath(effectiveRefPath, script_dir)
"""
def test_signatures_recognized(self):
with isolate_warnings():
- found_sigs = enum_all()
+ found_sigs = enum_all() # noqa: F841
if check_warnings():
raise RuntimeError("There are errors, see above.")
if key.startswith("sample.SampleNamespace"):
# We cannot work with sample namespaces after the change to __qualname__.
continue
- if (key.startswith("smart.SharedPtr") or
- re.match(r"PySide6\..*?\.QSharedPointer_", key)):
+ if (key.startswith("smart.SharedPtr")
+ or re.match(r"PySide6\..*?\.QSharedPointer_", key)):
# These mangled names are not supported.
# We should fix them.
continue
warn(f"missing key: '{key} value={value}'", stacklevel=3)
else:
found_val = found_sigs[key]
- if type(value) is list and (
- type(found_val) is tuple or
- len(found_val) < len(value)):
+ if type(value) is list and ( # noqa: W504
+ type(found_val) is tuple
+ or len(found_val) < len(value)):
# We check that nothing got lost. But it is ok when an older
# registry file does not know all variants, yet!
warn(multi_signature_msg(key, found_val, value), stacklevel=3)
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
+PYSIDE_TEST(anonymous_slot_leak_test.py)
PYSIDE_TEST(args_dont_match_test.py)
PYSIDE_TEST(bug_79.py)
PYSIDE_TEST(bug_189.py)
PYSIDE_TEST(qobject_destroyed_test.py)
PYSIDE_TEST(qobject_receivers_test.py)
PYSIDE_TEST(qobject_sender_test.py)
+PYSIDE_TEST(nonqobject_receivers_test.py)
PYSIDE_TEST(ref01_test.py)
PYSIDE_TEST(ref02_test.py)
PYSIDE_TEST(ref03_test.py)
from init_paths import init_test_paths
init_test_paths(True)
-from PySide6.QtCore import QObject, Signal
+from PySide6.QtCore import QObject, Signal, Slot, SIGNAL, SLOT
from testbinding import TestObject
-class Foo(QObject):
+class Sender(QObject):
bar = Signal()
+class Receiver(QObject):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.called = 0
+
+ @Slot()
+ def receiver(self):
+ self.called += 1
+
+
class TestDisconnect(unittest.TestCase):
def theSlot1(self):
self.called1 = True
def testIt(self):
self.called1 = False
self.called2 = False
- f = Foo()
- f.bar.connect(self.theSlot1)
- f.bar.connect(self.theSlot2)
- f.bar.emit()
+ s = Sender()
+ s.bar.connect(self.theSlot1)
+ s.bar.connect(self.theSlot2)
+ s.bar.emit()
self.assertTrue(self.called1)
self.assertTrue(self.called2)
self.called1 = False
self.called2 = False
- f.bar.disconnect()
- f.bar.emit()
+ self.assertTrue(s.bar.disconnect()) # Disconnect sender
+ s.bar.emit()
self.assertFalse(self.called1)
self.assertFalse(self.called2)
+ def testCallable(self):
+ s = Sender()
+ r = Receiver()
+ s.bar.connect(r.receiver)
+ s.bar.emit()
+ self.assertEqual(r.called, 1)
+ self.assertTrue(s.bar.disconnect(r.receiver))
+ s.bar.emit()
+ self.assertEqual(r.called, 1)
+
+ def testStringBased(self):
+ s = Sender()
+ r = Receiver()
+ QObject.connect(s, SIGNAL("bar()"), r, SLOT("receiver()"))
+ s.bar.emit()
+ self.assertEqual(r.called, 1)
+ self.assertTrue(QObject.disconnect(s, SIGNAL("bar()"), r, SLOT("receiver()")))
+ s.bar.emit()
+ self.assertEqual(r.called, 1)
+
+ def testMixStringBasedCallable(self):
+ """PYSIDE-3020, Disconnect a string-based connection by passing a callable."""
+ s = Sender()
+ r = Receiver()
+ QObject.connect(s, SIGNAL("bar()"), r, SLOT("receiver()"))
+ s.bar.emit()
+ self.assertEqual(r.called, 1)
+ self.assertTrue(s.bar.disconnect(r.receiver))
+ s.bar.emit()
+ self.assertEqual(r.called, 1)
+
+ def testMixCallableStringBased(self):
+ """PYSIDE-3020, test vice versa."""
+ s = Sender()
+ r = Receiver()
+ s.bar.connect(r.receiver)
+ s.bar.emit()
+ self.assertEqual(r.called, 1)
+ self.assertTrue(QObject.disconnect(s, SIGNAL("bar()"), r, SLOT("receiver()")))
+ s.bar.emit()
+ self.assertEqual(r.called, 1)
+
+ def testMetaMethod(self):
+ s = Sender()
+ r = Receiver()
+ sender_mo = s.metaObject()
+ signal_index = sender_mo.indexOfMethod("bar()")
+ self.assertTrue(signal_index != -1)
+ signal_method = sender_mo.method(signal_index)
+ receiver_mo = r.metaObject()
+ slot_index = receiver_mo.indexOfMethod("receiver()")
+ self.assertTrue(slot_index != -1)
+ slot_method = receiver_mo.method(slot_index)
+ conn_id = QObject.connect(s, signal_method, r, slot_method)
+ self.assertTrue(conn_id)
+ s.bar.emit()
+ self.assertEqual(r.called, 1)
+ self.assertTrue(QObject.disconnect(s, signal_method, r, slot_method))
+ s.bar.emit()
+ self.assertEqual(r.called, 1)
+
def testDuringCallback(self):
""" Test to see if the C++ object for a connection is accessed after the
method returns. This causes a segfault if the memory that was used by the
--- /dev/null
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+'''Test case for non-QObject.receivers'''
+
+import os
+import sys
+import unittest
+
+from pathlib import Path
+sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
+from init_paths import init_test_paths # noqa: F401
+init_test_paths(False)
+
+from helper.usesqapplication import UsesQApplication # noqa: F401
+
+from PySide6.QtGui import QAction # noqa: F401
+
+
+receiver_instances = 0
+
+
+class Receiver:
+
+ def __init__(self):
+ global receiver_instances
+ receiver_instances += 1
+ self.slot1Triggered = 0
+ self.slot2Triggered = 0
+
+ def __del__(self):
+ global receiver_instances
+ receiver_instances -= 1
+
+ def slot1(self):
+ self.slot1Triggered += 1
+
+ def slot2(self):
+ self.slot2Triggered += 1
+
+
+class TestQObjectReceivers(UsesQApplication):
+ '''Test case for non-QObject.receivers'''
+
+ @unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
+ def testBasic(self):
+ '''The test verifies that connections to methods of a non-QObject work
+ (TrackingMethodDynamicSlot). Also, despite being stored in the signal manager,
+ making connections should not increase references on the receiver or prevent
+ the receivers from being deleted, which is achieved using weak reference
+ tracking notifications.
+ 2 connections are used to trigger a corruption caused by multiple weak reference
+ notifications (PYSIDE-3148).'''
+ action1 = QAction("a1", qApp) # noqa: F821
+ action2 = QAction("a2", qApp) # noqa: F821
+ receiver = Receiver()
+ self.assertEqual(receiver_instances, 1)
+ base_ref_count = sys.getrefcount(receiver)
+ action1.triggered.connect(receiver.slot1)
+ action2.triggered.connect(receiver.slot2)
+ self.assertEqual(sys.getrefcount(receiver), base_ref_count)
+ action1.trigger()
+ action2.trigger()
+ self.assertEqual(receiver.slot1Triggered, 1)
+ self.assertEqual(receiver.slot2Triggered, 1)
+ receiver = 0
+ self.assertEqual(receiver_instances, 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
def destroyedSlot():
pass
- self.assertEqual(sys.getrefcount(destroyedSlot), 2)
+ base_ref_count = sys.getrefcount(destroyedSlot)
self.emitter.destroyed.connect(destroyedSlot)
- self.assertEqual(sys.getrefcount(destroyedSlot), 3)
+ self.assertEqual(sys.getrefcount(destroyedSlot), base_ref_count + 1)
self.emitter.destroyed.disconnect(destroyedSlot)
- self.assertEqual(sys.getrefcount(destroyedSlot), 2)
+ self.assertEqual(sys.getrefcount(destroyedSlot), base_ref_count)
if __name__ == '__main__':
def cb(*args):
pass
- self.assertEqual(sys.getrefcount(cb), 2)
+ base_ref_count = sys.getrefcount(cb)
self.emitter.foo.connect(cb)
- self.assertEqual(sys.getrefcount(cb), 3)
+ self.assertEqual(sys.getrefcount(cb), base_ref_count + 1)
self.emitter.foo.disconnect(cb)
- self.assertEqual(sys.getrefcount(cb), 2)
+ self.assertEqual(sys.getrefcount(cb), base_ref_count)
class CppSignalRefCount(unittest.TestCase):
def cb(*args):
pass
- self.assertEqual(sys.getrefcount(cb), 2)
+ base_ref_count = sys.getrefcount(cb)
self.emitter.destroyed.connect(cb)
- self.assertEqual(sys.getrefcount(cb), 3)
+ self.assertEqual(sys.getrefcount(cb), base_ref_count + 1)
self.emitter.destroyed.disconnect(cb)
- self.assertEqual(sys.getrefcount(cb), 2)
+ self.assertEqual(sys.getrefcount(cb), base_ref_count)
if __name__ == '__main__':
self._dir = Path(__file__).parent.resolve()
pyside_root = self._dir.parents[4]
self._metaobjectdump_tool = pyside_root / "sources" / "pyside-tools" / "metaobjectdump.py"
- self._examples_dir = (pyside_root / "examples" /
- "qml" / "tutorials" / "extending-qml-advanced")
+ self._examples_dir = (pyside_root / "examples"
+ / "qml" / "tutorials" / "extending-qml-advanced")
# Compile a list of examples (tuple [file, base line, command])
examples = []
android_requirements_file = pyside_tools / "requirements-android.txt"
with open(android_requirements_file, 'r', encoding='UTF-8') as file:
while line := file.readline():
- dependent_package = line.rstrip()
+ dependent_package = line.rstrip().split('==')[0]
if not bool(importlib.util.find_spec(dependent_package)):
command = [sys.executable, "-m", "pip", "install", dependent_package]
subprocess.run(command)
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
from __future__ import annotations
+import json
import unittest
import tempfile
import shutil
import importlib
import platform
from pathlib import Path
+from typing import Any
from unittest.mock import patch
from unittest import mock
def is_pyenv_python():
pyenv_root = os.environ.get("PYENV_ROOT")
-
if pyenv_root and (resolved_exe := str(Path(sys.executable).resolve())):
return resolved_exe.startswith(pyenv_root)
return False
-class LongSortedOptionTest(unittest.TestCase):
+class DeployTestBase(unittest.TestCase):
@staticmethod
- def _option_prepare(s):
+ def _sort_command(command: str) -> list[str]:
"""
- Take a string and return a list obtained by text.split().
- Options starting with "--" are also sorted."
+ Given a command, returns a list obtained by text.split().
+ Options starting with "--" are also sorted.
"""
- items = s.split()
+ items = command.split()
for idx in range(len(items)):
if items[idx].startswith("--"):
return items[:idx] + sorted(items[idx:])
return items
- def assertEqual(self, text_a, text_b):
- if (not isinstance(text_a, str) or not isinstance(text_b, str)
- or (len(text_a) < 50 and len(text_b) < 50)):
- return super().assertEqual(text_a, text_b)
- sort_a = self._option_prepare(text_a)
- sort_b = self._option_prepare(text_b)
- return super().assertEqual(sort_a, sort_b)
+ def assertCmdEqual(self, first: str, second: str, msg: Any = None):
+ """
+ Assert that two commands are equal. Sort their arguments
+ """
+ if not isinstance(first, str) or not isinstance(second, str):
+ return super().assertEqual(first, second, msg)
+ return super().assertEqual(self._sort_command(first), self._sort_command(second), msg)
-class DeployTestBase(LongSortedOptionTest):
@classmethod
def setUpClass(cls):
cls.pyside_root = Path(__file__).parents[5].resolve()
sys.path.append(str(cls.pyside_root / "sources" / "pyside-tools"))
cls.deploy_lib = importlib.import_module("deploy_lib")
cls.deploy = importlib.import_module("deploy")
+ cls.project_lib = importlib.import_module("project_lib")
sys.modules["deploy"] = cls.deploy
files_to_ignore = [".cpp.o", ".qsb"]
cls.dlls_ignore_nuitka = " ".join([f"--noinclude-dlls=*{file}"
"imageformats", "platforminputcontexts", "platforms",
"platformthemes", "styles", "xcbglintegrations"]
# Plugins that needs to be passed to Nuitka
- plugins_nuitka = ("platforminputcontexts")
+ plugins_nuitka = "platforminputcontexts"
self.expected_run_cmd = (
f"{sys.executable} -m nuitka {str(self.main_file)} --follow-imports"
f" --enable-plugin=pyside6 --output-dir={str(self.deployment_files)} --quiet"
def testWidgetDryRun(self, mock_plugins):
mock_plugins.return_value = self.all_plugins
- # Checking for dry run commands is equivalent to mocking the
- # subprocess.check_call() in commands.py as the the dry run command
- # is the command being run.
+ # Checking for dry run commands is equivalent to mocking the subprocess.check_call()
+ # in commands.py as the dry run command is the command being run.
original_output = self.deploy.main(self.main_file, dry_run=True, force=True)
- self.assertEqual(original_output, self.expected_run_cmd)
+ self.assertCmdEqual(original_output, self.expected_run_cmd)
@patch("deploy_lib.dependency_util.QtDependencyReader.get_qt_libs_dir")
def testWidgetConfigFile(self, mock_sitepackages, mock_plugins):
# includes both dry run and config_file tests
# init
init_result = self.deploy.main(self.main_file, init=True, force=True)
- self.assertEqual(init_result, None)
+ self.assertEqual(None, init_result)
# test with config
original_output = self.deploy.main(config_file=self.config_file, dry_run=True, force=True)
- self.assertEqual(original_output, self.expected_run_cmd)
+ self.assertCmdEqual(original_output, self.expected_run_cmd)
- # # test config file contents
+ # test config file contents
config_obj = self.deploy_lib.BaseConfig(config_file=self.config_file)
self.assertTrue(config_obj.get_value("app", "input_file").endswith("tetrix.py"))
- self.assertTrue(config_obj.get_value("app", "project_dir").endswith("tetrix"))
+ self.assertEqual(config_obj.get_value("app", "project_dir"), ".")
self.assertEqual(config_obj.get_value("app", "exec_directory"), ".")
self.assertEqual(config_obj.get_value("python", "packages"),
- "Nuitka==2.5.1")
+ "Nuitka==2.7.11")
self.assertEqual(config_obj.get_value("qt", "qml_files"), "")
equ_base = "--quiet --noinclude-qt-translations"
equ_value = equ_base + " --static-libpython=no" if is_pyenv_python() else equ_base
def testErrorReturns(self, mock_plugins):
mock_plugins.return_value = self.all_plugins
- # main file and config file does not exists
+ # Main file and config file do not exist
fake_main_file = self.main_file.parent / "main.py"
with self.assertRaises(RuntimeError) as context:
self.deploy.main(main_file=fake_main_file, config_file=self.config_file)
def testStandaloneMode(self, mock_plugins):
mock_plugins.return_value = self.all_plugins
- # remove --onefile from self.expected_run_cmd and replace it with --standalone
+ # Remove --onefile from self.expected_run_cmd and replace it with --standalone
self.expected_run_cmd = self.expected_run_cmd.replace(" --onefile", " --standalone")
- # test standalone mode
+ # Test standalone mode
original_output = self.deploy.main(self.main_file, mode="standalone", dry_run=True,
force=True)
- self.assertEqual(original_output, self.expected_run_cmd)
+ self.assertCmdEqual(original_output, self.expected_run_cmd)
@patch("deploy_lib.dependency_util.QtDependencyReader.get_qt_libs_dir")
def testExtraModules(self, mock_sitepackages, mock_plugins):
mock_plugins.return_value = self.all_plugins
init_result = self.deploy.main(self.main_file, extra_modules_grouped="QtNetwork,QtOpenGL",
init=True, force=True)
- self.assertEqual(init_result, None)
+ self.assertEqual(None, init_result)
self.deploy.main(config_file=self.config_file, dry_run=True, force=True)
- # test config file contents
+ # Test config file contents
config_obj = self.deploy_lib.BaseConfig(config_file=self.config_file)
expected_modules = {"Core", "Gui", "Widgets", "Network", "OpenGL"}
if sys.platform != "win32":
@patch("deploy_lib.dependency_util.QtDependencyReader.get_qt_libs_dir")
def testExtraIgnoreDirs(self, mock_sitepackages, mock_plugins):
- # create a directory to ignore
+ # Create a directory to ignore
ignore_dir = self.temp_example_widgets / "ignore_dir"
ignore_dir.mkdir()
ignore_file = ignore_dir / "test_ignore.py"
mock_plugins.return_value = self.all_plugins
init_result = self.deploy.main(self.main_file, extra_ignore_dirs="ignore_dir",
init=True, force=True)
- self.assertEqual(init_result, None)
+ self.assertEqual(None, init_result)
self.deploy.main(config_file=self.config_file, dry_run=True, force=True)
config_obj = self.deploy_lib.BaseConfig(config_file=self.config_file)
"platformthemes", "qmltooling", "tls",
"xcbglintegrations"]
# Plugins that needs to be passed to Nuitka
- plugins_nuitka = ("networkinformation,platforminputcontexts,qml,qmltooling")
+ plugins_nuitka = "networkinformation,platforminputcontexts,qml,qmltooling"
self.expected_run_cmd = (
f"{sys.executable} -m nuitka {str(self.main_file)} --follow-imports"
f" --enable-plugin=pyside6 --output-dir={str(self.deployment_files)} --quiet"
with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner:
mock_qmlimportscanner.return_value = ["QtQuick"]
init_result = self.deploy.main(self.main_file, init=True, force=True)
- self.assertEqual(init_result, None)
+ self.assertEqual(None, init_result)
# test config file contents
config_obj = self.deploy_lib.BaseConfig(config_file=self.config_file)
self.assertTrue(config_obj.get_value("app", "input_file").endswith("main.py"))
- self.assertTrue(config_obj.get_value("app", "project_dir").endswith("editingmodel"))
+ self.assertEqual(config_obj.get_value("app", "project_dir"), ".")
self.assertEqual(config_obj.get_value("app", "exec_directory"), ".")
self.assertEqual(config_obj.get_value("python", "packages"),
- "Nuitka==2.5.1")
- self.assertEqual(config_obj.get_value("qt", "qml_files"), "main.qml,MovingRectangle.qml")
+ "Nuitka==2.7.11")
+ self.assertEqual(config_obj.get_value("qt", "qml_files"),
+ "MovingRectangle.qml,main.qml")
equ_base = "--quiet --noinclude-qt-translations"
equ_value = equ_base + " --static-libpython=no" if is_pyenv_python() else equ_base
self.assertEqual(config_obj.get_value("nuitka", "extra_args"), equ_value)
)
expected_modules = {"Core", "Gui", "Qml", "Quick", "Network", "OpenGL", "QmlModels",
"QmlWorkerScript", "QmlMeta"}
+
+ # Exclude OpenGL if the platform is Windows and architecture is aarch64
+ # https://bugreports.qt.io/browse/QTBUG-126030
+ if sys.platform == "win32" and platform.machine() == "ARM64":
+ expected_modules.remove("OpenGL")
+
if sys.platform != "win32":
expected_modules.add("DBus")
obtained_modules = set(config_obj.get_value("qt", "modules").split(","))
with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner:
mock_qmlimportscanner.return_value = ["QtQuick"]
original_output = self.deploy.main(self.main_file, dry_run=True, force=True)
- self.assertEqual(original_output, self.expected_run_cmd)
+ self.assertCmdEqual(original_output, self.expected_run_cmd)
self.assertEqual(mock_qmlimportscanner.call_count, 1)
def testMainFileDryRun(self, mock_plugins):
with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner:
mock_qmlimportscanner.return_value = ["QtQuick"]
original_output = self.deploy.main(Path.cwd() / "main.py", dry_run=True, force=True)
- self.assertEqual(original_output, self.expected_run_cmd)
+ self.assertCmdEqual(original_output, self.expected_run_cmd)
self.assertEqual(mock_qmlimportscanner.call_count, 1)
main_file = self.temp_example_webenginequick / "quicknanobrowser.py"
deployment_files = self.temp_example_webenginequick / "deployment"
# Plugins that needs to be passed to Nuitka
- plugins_nuitka = ("networkinformation,platforminputcontexts,qml,qmltooling")
+ plugins_nuitka = "networkinformation,platforminputcontexts,qml,qmltooling"
qml_files = [
"ApplicationRoot.qml",
"BrowserDialog.qml",
with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner:
mock_qmlimportscanner.return_value = ["QtQuick", "QtWebEngine"]
init_result = self.deploy.main(main_file, init=True, force=True)
- self.assertEqual(init_result, None)
+ self.assertEqual(None, init_result)
# run dry_run
original_output = self.deploy.main(main_file, dry_run=True, force=True)
expected_modules = {"Core", "Gui", "Quick", "Qml", "WebEngineQuick", "Network", "OpenGL",
"QmlModels", "QmlWorkerScript", "QmlMeta", "WebEngineCore",
"Positioning", "WebChannelQuick", "WebChannel"}
+
+ # Exclude specific modules if the platform is Windows and architecture is aarch64
+ if sys.platform == "win32" and platform.machine() == "ARM64":
+ excluded_modules = {"OpenGL", "WebEngineCore", "Positioning", "WebChannelQuick",
+ "WebChannel"}
+ expected_modules.difference_update(excluded_modules)
+
if sys.platform != "win32":
expected_modules.add("DBus")
+
obtained_modules = set(config_obj.get_value("qt", "modules").split(","))
self.assertEqual(obtained_modules, expected_modules)
@unittest.skipIf(sys.platform == "darwin" and int(platform.mac_ver()[0].split('.')[0]) <= 11,
"Test only works on macOS version 12+")
@patch("deploy_lib.config.QtDependencyReader.find_plugin_dependencies")
-class EmptyDSProjectTest(DeployTestBase):
+class TestEmptyDSProject(DeployTestBase):
@classmethod
def setUpClass(cls):
super().setUpClass()
- # setup a test DS Python project
+ # Set up a Qt Design Studio empty Python project
base_path = Path(cls.temp_dir) / "PythonProject"
project_name = "TestProject"
base_path / f"{project_name}.qrc"
]
- # Create the files
+ # Create the project files
for file in files:
file.parent.mkdir(parents=True, exist_ok=True)
file.touch(exist_ok=True)
+ # Create a project file in the Python folder
+ cls.pyproject_path = (base_path / "Python" / ".pyproject").resolve()
+ cls.pyproject_path.touch()
+ cls.pyproject_path.write_text(json.dumps({"files": ["main.py", "autogen/settings.py"]}))
+
cls.temp_example = base_path
def setUp(self):
def testDryRun(self, mock_plugins):
with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner: # noqa: F841
original_output = self.deploy.main(self.main_file, dry_run=True, force=True)
- self.assertEqual(self.expected_run_cmd, original_output)
+ self.assertCmdEqual(self.expected_run_cmd, original_output)
@patch("deploy_lib.dependency_util.QtDependencyReader.get_qt_libs_dir")
def testConfigFile(self, mock_sitepackages, mock_plugins):
mock_sitepackages.return_value = Path(_get_qt_lib_dir())
- # create config file
with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner: # noqa: F841
+ # Create the pysidedeploy.spec file only
init_result = self.deploy.main(self.main_file, init=True, force=True)
- self.assertEqual(init_result, None)
+ self.assertEqual(None, init_result)
# test config file contents
config_obj = self.deploy_lib.BaseConfig(config_file=self.config_file)
self.assertTrue(config_obj.get_value("app", "input_file").endswith("main.py"))
self.assertTrue(config_obj.get_value("app", "project_dir").endswith("PythonProject"))
+
+ expected_project_file = self.pyproject_path.relative_to(self.temp_example)
+ self.assertEqual(str(expected_project_file), config_obj.get_value("app", "project_file"))
+
self.config_file.unlink()
+++ /dev/null
-{
- "files": ["main.py", "autogen/settings.py"]
-}
--- /dev/null
+{
+ "files": ["main.py", "autogen/settings.py"]
+}
--- /dev/null
+[project]
+name = "Drumpad"
+
+[tool.pyside6-project]
+files = ["autogen/settings.py", "main.py"]
--- /dev/null
+{
+ "files": ["mainwindow.py", "my_widget.py", "folder/file_in_folder.py", "main.py", "subproject/subproject.pyproject"]
+}
--- /dev/null
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+from PySide6.QtWidgets import QLabel
+
+
+class LabelInFolder(QLabel):
+ def __init__(self):
+ super().__init__()
+ self.setText("Label in folder")
--- /dev/null
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+import os
+
+from mainwindow import MainWindow
+from PySide6.QtWidgets import QApplication
+import sys
+
+
+def main():
+ app = QApplication(sys.argv)
+ window = MainWindow()
+ if os.getenv("PYSIDE_TESTING"):
+ return 0
+ window.show()
+ return app.exec()
+
+
+if __name__ == "__main__":
+ sys.exit(main())
--- /dev/null
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+from PySide6.QtWidgets import QMainWindow, QWidget, QVBoxLayout
+from folder.label_in_folder import LabelInFolder
+from subproject.subproject_button import SubprojectButton
+
+
+class MainWindow(QMainWindow):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.setWindowTitle("Main Window")
+
+ self.central_layout = QVBoxLayout()
+ self.central_widget = QWidget()
+ self.setCentralWidget(self.central_widget)
+ self.central_widget.setLayout(self.central_layout)
+
+ self.label_in_folder = LabelInFolder()
+ self.central_layout.addWidget(self.label_in_folder)
+
+ self.subproject_button = SubprojectButton()
+ self.central_layout.addWidget(self.subproject_button)
--- /dev/null
+[project]
+name = "example_project"
+
+[tool.pyside6-project]
+files = ["folder/file_in_folder.py", "main.py", "mainwindow.py", "my_widget.py", "subproject/subproject.pyproject"]
--- /dev/null
+[project]
+name = "subproject"
+
+[tool.pyside6-project]
+files = ["subproject_button.py"]
--- /dev/null
+{
+ "files": ["subproject_button.py"]
+}
--- /dev/null
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+from PySide6.QtWidgets import QPushButton, QApplication
+import sys
+
+
+class SubprojectButton(QPushButton):
+ def __init__(self):
+ super().__init__()
+ self.setText("Subproject button")
+
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+ button = SubprojectButton()
+ button.show()
+ sys.exit(app.exec())
--- /dev/null
+{
+ "files": ["zzz.py", "main.py"]
+}
--- /dev/null
+[project]
+name = "my_project"
+version = "0.1.0"
+description = "A sample Python project"
+authors = [
+ { name = "John Doe", email = "john.doe@example.com" },
+]
+optional-dependencies = { dev = ["pytest", "black"], docs = ["sphinx"] }
+
+# Comment
+
+[tool.black]
+line-length = 88
+target-version = ["py38"]
+
+# Another comment
+
+[tool.pyside6-project]
+files = ["main.py", "zzz.py"]
+[build-system]
+requires = ["setuptools >=42"]
+build-backend = "setuptools.build_meta"
--- /dev/null
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
--- /dev/null
+[project]
+name = "my_project"
+version = "0.1.0"
+description = "A sample Python project"
+authors = [
+ { name = "John Doe", email = "john.doe@example.com" },
+]
+optional-dependencies = { dev = ["pytest", "black"], docs = ["sphinx"] }
+
+# Comment
+
+[tool.black]
+line-length = 88
+target-version = ["py38"]
+
+# Another comment
+
+[build-system]
+requires = ["setuptools >=42"]
+build-backend = "setuptools.build_meta"
--- /dev/null
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
--- /dev/null
+{
+ "files": ["main.py", 33]
+}
--- /dev/null
+this is not a valid pyproject.toml file
+because it does not have a valid toml structure
--- /dev/null
+{
+ "files": ["main.py"]
+}
--- /dev/null
+[project]
+name = "multiple_pyproject"
+
+[tool.pyside6-project]
+files = ["common_file.py", "file1.py", "file2.py"]
--- /dev/null
+{
+ "files": ["file1.py", "common_file.py"]
+}
--- /dev/null
+{
+ "files": ["common_file.py", "file2.py"]
+}
# Copyright (C) 2024 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
import contextlib
+import difflib
+import importlib
import io
import os
import shutil
import sys
-import unittest
-from unittest import mock
-from unittest import TestCase
import tempfile
-import importlib
+import unittest
from pathlib import Path
+from unittest import TestCase
+from unittest import mock
+from unittest.mock import patch
sys.path.append(str(Path(__file__).resolve().parents[2]))
from init_paths import init_test_paths
init_test_paths(False)
+def file_diff(expected_file: Path, actual_file: Path) -> str:
+ """
+ Get a unified diff between two files
+ """
+ target_text = expected_file.read_text(encoding="utf-8").splitlines()
+ generated_text = actual_file.read_text(encoding="utf-8").splitlines()
+
+ return "\n".join(difflib.unified_diff(
+ generated_text, target_text,
+ fromfile=str(actual_file),
+ tofile=str(expected_file),
+ lineterm=""
+ ))
+
+
class PySide6ProjectTestBase(TestCase):
+ # If a project name is specified, on each the test, the project folder will be copy to the
+ # temp dir and the current dir will be changed to the project folder
+ # The project name must match an existing folder in the folder where this file is located
+ project_name: str | None = None
+
@classmethod
def setUpClass(cls):
cls.pyside_root = Path(__file__).parents[5].resolve()
- cls.example_root = cls.pyside_root / "examples"
tools_path = cls.pyside_root / "sources" / "pyside-tools"
if tools_path not in sys.path:
sys.path.append(str(tools_path))
cls.current_dir = Path.cwd()
# print no outputs to stdout
sys.stdout = mock.MagicMock()
+ if cls.project_name:
+ cls.temp_project = Path(cls.temp_dir / cls.project_name).resolve()
+ os.chdir(cls.temp_dir)
+
+ def setUp(self):
+ super().setUp()
+ if self.project_name:
+ shutil.copytree(Path(__file__).parent / self.project_name, self.temp_project)
+ os.chdir(self.temp_project)
+
+ def tearDown(self):
+ super().tearDown()
+ if self.project_name:
+ os.chdir(self.temp_dir)
+ shutil.rmtree(self.temp_project)
@classmethod
def tearDownClass(cls):
os.chdir(cls.current_dir)
shutil.rmtree(cls.temp_dir)
- def setUp(self):
- os.chdir(self.temp_dir)
-
class TestPySide6ProjectDesignStudio(PySide6ProjectTestBase):
- @classmethod
- def setUpClass(cls):
- super().setUpClass()
- example_drumpad = Path(__file__).parent / "example_drumpad"
- cls.temp_example_drumpad = Path(
- shutil.copytree(example_drumpad, cls.temp_dir / "drumpad")
- ).resolve()
+ project_name = "example_drumpad"
def testDrumpadExample(self):
# This test compiles the .qrc file into a .py file and checks whether the compilation is
# carried out only when required
- compiled_resources_path = self.temp_example_drumpad / "Python" / "autogen" / "resources.py"
- resources_path = self.temp_example_drumpad / "Drumpad.qrc"
- requires_rebuild = self.project_lib.utils.requires_rebuild
+ compiled_resources_path = Path("Python") / "autogen" / "resources.py"
+ resources_path = Path("Drumpad.qrc")
+ requires_rebuild = self.project_lib.utils.requires_rebuild
+ pyproject_path = Path("Python") / "Drumpad.pyproject"
self.assertFalse(compiled_resources_path.exists())
-
- os.chdir(self.temp_example_drumpad / "Python")
- self.project.main(mode="build")
+ self.assertTrue(pyproject_path.exists())
+ self.project.main(mode="build", project_path=pyproject_path)
self.assertTrue(compiled_resources_path.exists())
self.assertFalse(requires_rebuild([resources_path], compiled_resources_path))
self.assertTrue(requires_rebuild([resources_path], compiled_resources_path))
- self.project.main(mode="build")
+ self.project.main(mode="build", project_path=pyproject_path)
self.assertFalse(requires_rebuild([resources_path], compiled_resources_path))
# Refresh the modification timestamp of one of the resources files
- list((self.temp_example_drumpad / "Resources").glob("*.txt"))[0].touch()
+ list((Path("Resources").glob("*.txt")))[0].touch()
self.assertTrue(requires_rebuild([resources_path], compiled_resources_path))
- self.project.main(mode="clean")
+ self.project.main(mode="clean", project_path=pyproject_path)
self.assertFalse(compiled_resources_path.exists())
+ def testMigrateDrumpadExample(self):
+ # The pyproject.toml file already contains the expected output
+ expected_pyproject_toml = Path("Python") / "pyproject.toml"
+ expected_pyproject_toml.rename(expected_pyproject_toml.parent / "expected_pyproject.toml")
+ existing_pyproject = Path("Python") / "Drumpad.pyproject"
+
+ with self.assertRaises(SystemExit) as context:
+ with patch("builtins.input", return_value="y"):
+ self.project.main(mode="migrate-pyproject",
+ project_path=existing_pyproject.as_posix())
+
+ self.assertEqual(0, context.exception.code)
+ generated_pyproject_toml = Path("Python") / "pyproject.toml"
+ self.assertTrue(generated_pyproject_toml.exists())
+ diff = file_diff(expected_pyproject_toml, generated_pyproject_toml)
+ self.assertFalse(diff, f"Generated pyproject.toml does not match:\n{diff}")
+
class TestPySide6ProjectNew(PySide6ProjectTestBase):
def testNewUi(self):
+ test_project_path = self.temp_dir / "NewUiProject"
with self.assertRaises(SystemExit) as context:
- self.project.main(mode="new-ui", file="TestProject")
- test_project_path = Path("TestProject")
- self.assertTrue((test_project_path / "TestProject.pyproject").exists())
+ self.project.main(mode="new-ui", project_dir=test_project_path.as_posix())
+
+ self.assertTrue((test_project_path / "pyproject.toml").exists())
self.assertTrue((test_project_path / "mainwindow.ui").exists())
self.assertTrue((test_project_path / "main.py").exists())
- self.assertEqual(context.exception.code, 0)
+ self.assertEqual(0, context.exception.code)
shutil.rmtree(test_project_path)
- def testRaiseErrorOnExistingProject(self):
+ def testRaiseErrorOnExistingNonEmptyProject(self):
+ # Create a project twice to ensure that an error is raised
+ project_name = "TestProject"
with self.assertRaises(SystemExit) as context:
- self.project.main(mode="new-ui", file="TestProject")
- self.assertEqual(context.exception.code, 0)
+ self.project.main(mode="new-ui", project_dir=project_name)
+
+ self.assertEqual(0, context.exception.code)
+
error_message = io.StringIO()
- with self.assertRaises(SystemExit) as context, contextlib.redirect_stderr(error_message):
- self.project.main(mode="new-ui", file="TestProject")
- self.assertEqual(context.exception.code, -1)
- self.assertTrue(error_message.getvalue()) # some error message is printed
+ with self.assertRaises(SystemExit) as context:
+ with contextlib.redirect_stderr(error_message):
+ self.project.main(mode="new-ui", project_dir=project_name)
+
+ self.assertEqual(1, context.exception.code)
+ self.assertTrue(f"Can not create project at {project_name}: directory is not empty." in
+ error_message.getvalue())
shutil.rmtree(self.temp_dir / "TestProject")
+ def testRaiseErrorOnInvalidProjectName(self):
+ # Create a project with an empty project name
+ error_message = io.StringIO()
+ with self.assertRaises(SystemExit) as context:
+ with contextlib.redirect_stderr(error_message):
+ self.project.main(mode="new-ui", project_dir="asdf/?^%$#@!")
+
+ self.assertEqual(1, context.exception.code)
+ self.assertTrue("Invalid project name" in error_message.getvalue())
+
def testNewQuick(self):
+ test_project_path = Path("QuickProject")
+
with self.assertRaises(SystemExit) as context:
- self.project.main(mode="new-quick", file="TestProject")
- test_project_path = Path("TestProject")
- self.assertTrue((test_project_path / "TestProject.pyproject").exists())
+ self.project.main(mode="new-quick", project_dir=str(test_project_path))
+
+ self.assertTrue((test_project_path / "pyproject.toml").exists())
self.assertTrue((test_project_path / "main.qml").exists())
self.assertTrue((test_project_path / "main.py").exists())
- self.assertEqual(context.exception.code, 0)
+ self.assertEqual(0, context.exception.code)
shutil.rmtree(test_project_path)
def testNewWidget(self):
+ project_dir = self.temp_dir / "inner_folder" / "another_folder" / "WidgetProject"
with self.assertRaises(SystemExit) as context:
- self.project.main(mode="new-widget", file="TestProject")
- test_project_path = Path("TestProject")
- self.assertTrue((test_project_path / "TestProject.pyproject").exists())
- self.assertTrue((test_project_path / "main.py").exists())
- self.assertEqual(context.exception.code, 0)
- shutil.rmtree(test_project_path)
+ self.project.main(mode="new-widget", project_dir=project_dir.as_posix())
+ self.assertTrue((project_dir / "pyproject.toml").exists())
+ self.assertTrue((project_dir / "main.py").exists())
+ self.assertEqual(0, context.exception.code)
+ shutil.rmtree(project_dir)
def testRaiseErrorWhenNoProjectNameIsSpecified(self):
+ mode = "new-widget"
error_message = io.StringIO()
with self.assertRaises(SystemExit) as context, contextlib.redirect_stderr(error_message):
- self.project.main(mode="new-widget", file="")
+ self.project.main(mode=mode)
self.assertEqual(context.exception.code, 1)
- self.assertTrue(error_message.getvalue()) # some error message is printed
+ expected_msg = f"Error creating new project: {mode} requires a directory name or path"
+ self.assertTrue(expected_msg in error_message.getvalue())
+
+ def testCreateProjectLegacyPyProjectFile(self):
+ project_path = Path("TestPyProjectJSON")
+ mode = "new-widget"
+ with self.assertRaises(SystemExit) as context:
+ self.project.main(mode=mode, project_dir=project_path.as_posix(), legacy_pyproject=True)
+ self.assertEqual(0, context.exception.code)
+ self.assertTrue((project_path / "main.py").exists())
+ self.assertTrue((project_path / f"{project_path.name}.pyproject").exists())
class TestPySide6ProjectRun(PySide6ProjectTestBase):
- @classmethod
- def setUpClass(cls):
- super().setUpClass()
- example_widgets = cls.example_root / "widgets" / "widgets" / "tetrix"
- cls.temp_example_tetrix = Path(
- shutil.copytree(example_widgets, Path(cls.temp_dir) / "tetrix")
- ).resolve()
-
- def testRunEmptyProject(self):
- project_folder = self.temp_dir / "TestProject"
+ project_name = "example_project"
+
+ def testRaiseErrorWhenRunningEmptyProject(self):
+ # Create a new empty project in the temp dir
+ project_folder = self.temp_dir / "empty_project"
project_folder.mkdir()
os.chdir(project_folder)
+
error_message = io.StringIO()
- with self.assertRaises(SystemExit) as context, contextlib.redirect_stderr(error_message):
+ with self.assertRaises(SystemExit) as context:
+ with contextlib.redirect_stderr(error_message):
+ self.project.main(mode="run")
+
+ os.chdir(self.temp_dir)
+ shutil.rmtree(project_folder)
+
+ self.assertEqual(1, context.exception.code)
+ self.assertTrue("No project file found" in error_message.getvalue())
+
+ def testRunExampleProject(self):
+ # The project is executed in a subprocess. The proejct code reads the PYSIDE_TESTING
+ # environment variable to avoid starting the Qt event loop
+ os.environ["PYSIDE_TESTING"] = "1"
+ with self.assertRaises(SystemExit) as context:
self.project.main(mode="run")
- self.assertEqual(context.exception.code, 1)
- self.assertTrue(error_message.getvalue()) # some error message is printed
+ os.environ.pop("PYSIDE_TESTING")
+ self.assertEqual(0, context.exception.code)
+
+ self.assertEqual(Path("pyproject.toml").resolve(),
+ self.project_lib.resolve_valid_project_file())
+
+
+class TestPySide6ProjectExampleProject(PySide6ProjectTestBase):
+ """
+ Test of an example project with both pyproject.toml and .pyproject valid files.
+ Contains a subproject with its own pyproject.toml file and .pyproject file too
+ """
+ project_name = "example_project"
+
+ def testMigratePyProjectToToml(self):
+ # The existing pyproject.toml file contains the expected output
+ expected_pyproject_toml = Path("pyproject.toml").rename("expected_pyproject.toml")
+ expected_subproject_pyproject_toml = Path("subproject") / "pyproject.toml"
+ expected_subproject_pyproject_toml.rename(
+ expected_subproject_pyproject_toml.parent / "expected_subproject_pyproject.toml")
+
+ with self.assertRaises(SystemExit) as context:
+ self.project.main(mode="migrate-pyproject")
+
+ self.assertEqual(0, context.exception.code)
+
+ generated_pyproject_toml = Path("pyproject.toml")
+ self.assertTrue(generated_pyproject_toml.exists())
+ diff = file_diff(expected_pyproject_toml, generated_pyproject_toml)
+ self.assertFalse(diff, f"Generated pyproject.toml does not match:\n{diff}")
+
+ generated_subproject_pyproject_toml = Path("subproject") / "pyproject.toml"
+ self.assertTrue(generated_subproject_pyproject_toml.exists())
+ diff = file_diff(expected_subproject_pyproject_toml, generated_subproject_pyproject_toml)
+ self.assertFalse(diff, f"Generated subproject/pyproject.toml does not match:\n{diff}")
+
+ def testMigratePyProjectToTomlSpecifyingPyProjectFile(self):
+ # The existing pyproject.toml file contains the expected output
+ existing_pyproject = Path("example_project.pyproject")
+ expected_pyproject_toml = Path("pyproject.toml")
+ expected_pyproject_toml.rename("example_project.toml")
+
+ expected_subproject_pyproject_toml = Path("subproject") / "pyproject.toml"
+ expected_subproject_pyproject_toml.rename(
+ expected_subproject_pyproject_toml.parent / "expected_pyproject.toml")
+
+ with self.assertRaises(SystemExit) as context:
+ with patch("builtins.input", return_value="y"):
+ self.project.main(mode="migrate-pyproject",
+ project_path=existing_pyproject.as_posix())
+
+ self.assertEqual(0, context.exception.code)
+
+ generated_pyproject_toml = Path("pyproject.toml")
+ self.assertTrue(generated_pyproject_toml.exists())
+ diff = file_diff(expected_pyproject_toml, generated_pyproject_toml)
+ self.assertFalse(diff, f"Generated pyproject.toml does not match:\n{diff}")
+
+ generated_subproject_pyproject_toml = Path("subproject") / "pyproject.toml"
+ self.assertTrue(generated_subproject_pyproject_toml.exists())
+ diff = file_diff(expected_subproject_pyproject_toml, generated_subproject_pyproject_toml)
+ self.assertFalse(diff, f"Generated subproject/pyproject.toml does not match:\n{diff}")
+
+
+class TestPySide6ProjectExistingPyProjectToml(PySide6ProjectTestBase):
+ """
+ Test for migrating a project with an existing pyproject.toml file which does not contain the
+ [tool.pyside6-project] section
+ """
+ project_name = "existing_pyproject_toml"
+
+ def testMigratePyProjectToTomlAlreadyExistingTomlFile(self):
+ with self.assertRaises(SystemExit) as context:
+ with patch("builtins.input", return_value="y"):
+ self.project.main(mode="migrate-pyproject")
+
+ self.assertEqual(0, context.exception.code)
+ diff = file_diff(Path("expected_pyproject.toml"),
+ Path("pyproject.toml"))
+ self.assertFalse(diff, f"Updated pyproject.toml does not match:\n{diff}")
+
+
+class TestPySide6ProjectInvalidPyProjectToml(PySide6ProjectTestBase):
+ """
+ Check the current behavior in a project with an existing invalid pyproject.toml file and
+ invalid_pyproject.pyproject file
+ """
+
+ project_name = "invalid_pyproject"
+
+ def testRunInvalidPyProjectTomlFile(self):
+ pyproject_toml = Path("pyproject.toml")
+ self.assertTrue(pyproject_toml.exists())
+ self.assertTrue(self.project_lib.parse_pyproject_toml(pyproject_toml).errors)
+
+ error_message = io.StringIO()
+ with contextlib.redirect_stderr(error_message):
+ with self.assertRaises(SystemExit) as context:
+ self.project.main(mode="run", project_path=pyproject_toml.as_posix())
+
+ self.assertEqual(1, context.exception.code)
+ self.assertTrue("Invalid project file" in error_message.getvalue())
+
+ def testRunSpecifyingPyProjectJsonFile(self):
+ # Check that the *.pyproject file is used if the pyproject.toml is invalid when using
+ # pyside6-project run
+
+ pyproject_toml_file = Path("pyproject.toml")
+ self.assertTrue(pyproject_toml_file.exists())
+ # Ensure that pyproject.toml is considered invalid
+ self.assertTrue(self.project_lib.parse_pyproject_toml(pyproject_toml_file).errors)
+
+ valid_pyproject = Path("valid_pyproject.pyproject")
+ self.assertTrue(valid_pyproject.exists())
+
+ # Ensure that the project can still be run specifying a valid *.pyproject JSON file
+ with self.assertRaises(SystemExit) as context:
+ self.project.main(mode="run", project_path=valid_pyproject.as_posix())
+
+ self.assertEqual(0, context.exception.code)
+ self.assertTrue(Path("main.py").exists())
+
+ def testErrorRaisesWhenRunningWithoutSpecifyingProjectFile(self):
+ # The project folder contains two *.pyproject JSON files.
+ # The tool should raise an error because the project file is not specified
+ error_message = io.StringIO()
+ with contextlib.redirect_stderr(error_message):
+ with self.assertRaises(SystemExit) as context:
+ self.project.main(mode="run")
+ self.assertEqual(1, context.exception.code)
+ self.assertTrue("Multiple project files found" in error_message.getvalue())
+
+ def testRaiseErrorResolvingInvalidProjectFile(self):
+ # Simulate that the user is specifying an invalid project file
+ invalid_pyproject_file = Path("invalid_pyproject.pyproject")
+ self.assertTrue(invalid_pyproject_file.exists())
+
+ with self.assertRaises(ValueError) as context:
+ self.project_lib.resolve_valid_project_file(invalid_pyproject_file.as_posix())
+
+ exception_message = str(context.exception)
+ self.assertTrue("Invalid project file" in exception_message)
+ self.assertTrue(str(invalid_pyproject_file) in exception_message)
+
+ def testResolveValidProjectFile(self):
+ # Simulate that the user is specifying a valid project file
+ valid_pyproject_file = Path("valid_pyproject.pyproject")
+ actual_project_file = self.project_lib.resolve_valid_project_file(
+ valid_pyproject_file.as_posix())
+ self.assertEqual(valid_pyproject_file.resolve(), actual_project_file)
+
+ def testRaiseErrorResolvingInvalidPyProjectToml(self):
+ # Simulate that the user is specifying an invalid pyproject.toml file
+ pyproject_toml_file = Path("pyproject.toml")
+ self.assertTrue(pyproject_toml_file.exists())
+
+ with self.assertRaises(ValueError) as context:
+ self.project_lib.resolve_valid_project_file(pyproject_toml_file.as_posix())
+
+ exception_message = str(context.exception)
+ self.assertTrue("Invalid project file" in exception_message)
+ self.assertTrue(str(pyproject_toml_file) in exception_message)
+
+ def testMigrateInvalidPyProjectToml(self):
+ # Can not migrate a project with an invalid pyproject.toml file
+ error_message = io.StringIO()
+ with contextlib.redirect_stderr(error_message):
+ with self.assertRaises(SystemExit) as context:
+ with patch("builtins.input", return_value="y"):
+ self.project.main(mode="migrate-pyproject")
+
+ self.assertEqual(1, context.exception.code)
+ self.assertTrue("Invalid project file" in error_message.getvalue())
+
+ def testMigrateInvalidPyProjectTomlSpecifyingWrongFile(self):
+ # Test specifying the pyproject.toml file as the project file to be migrated
+ existing_invalid_pyproject_toml = Path("pyproject.toml")
+ self.assertTrue(
+ bool(self.project_lib.parse_pyproject_toml(existing_invalid_pyproject_toml).errors))
+
+ error_message = io.StringIO()
+ with contextlib.redirect_stderr(error_message):
+ with self.assertRaises(SystemExit) as context:
+ self.project.main(mode="migrate-pyproject",
+ project_path=existing_invalid_pyproject_toml)
+
+ self.assertEqual(1, context.exception.code)
+ self.assertTrue("Cannot migrate non \"*.pyproject\" file" in error_message.getvalue())
+ self.assertTrue("pyproject.toml" in error_message.getvalue())
+
+
+def testRunInvalidPyProjectToml(self):
+ # Ensure that the .pyproject file is preferred over the invalid pyproject.toml file.
+ # This preserves the backward compatibility of the .pyproject file
+
+ # Remove the invalid invalid_pyproject.pyproject file first
+ Path("invalid_pyproject.pyproject").unlink()
+ self.assertFalse(Path("invalid_pyproject.pyproject").exists())
+
+ with self.assertRaises(SystemExit) as context:
+ self.project.main(mode="run")
+
+ self.assertEqual(0, context.exception.code)
+ self.assertEqual(Path("valid_pyproject.pyproject").resolve(),
+ self.project_lib.resolve_valid_project_file())
+
+
+class TestPySide6ProjectMultiplePyProject(PySide6ProjectTestBase):
+ project_name = "multiple_pyproject"
+
+ def testCancelMigration(self):
+ # Ensure that the pyproject.toml is not created if the user cancels the operation
+ with self.assertRaises(SystemExit) as context:
+ with patch("builtins.input", return_value="n"):
+ self.project.main(mode="migrate-pyproject")
+
+ self.assertEqual(0, context.exception.code)
+ self.assertFalse(Path("pyproject.toml").exists())
+
+ def testMigrateMultiplePyProjectFilesToToml(self):
+ expected_pyproject_toml = Path("expected_pyproject.toml")
+ generated_pyproject_toml = Path("pyproject.toml")
+
+ with self.assertRaises(SystemExit) as context:
+ with patch("builtins.input", return_value="y"):
+ self.project.main(mode="migrate-pyproject")
+
+ self.assertEqual(0, context.exception.code)
+ self.assertTrue(generated_pyproject_toml.exists())
+ diff = file_diff(expected_pyproject_toml, generated_pyproject_toml)
+ self.assertFalse(diff, f"Generated pyproject.toml does not match:\n{diff}")
if __name__ == "__main__":
self.pyside_root = self._dir.parents[4]
self.pyqml_path = self.pyside_root / "sources" / "pyside-tools" / "qml.py"
- self.core_qml_path = (self.pyside_root / "examples" / "qml" /
- "tutorials" / "extending-qml-advanced" / "adding")
+ self.core_qml_path = (self.pyside_root / "examples" / "qml"
+ / "tutorials" / "extending-qml-advanced" / "adding")
self.pyqml_run_cmd = [sys.executable, os.fspath(self.pyqml_path)]
def tearDown(self):
try:
del self.args
- except:
+ except: # noqa: E722
pass
# PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
gc.collect()
# FIXME currently we have to define doc_filter on each subclass
filter_function = dct.get('doc_filter')
if not filter_function:
- filter_function = lambda x: False
+ filter_function = lambda x: False # noqa: E731
for base in bases:
for attr in [x for x in base.__dict__ if filter_function(x)]:
if original.__doc__:
copy = copy_func(original)
- copy.__doc__ = (dct.get('doc_prefix', '') +
- original.__doc__ +
- dct.get('doc_suffix', ''))
+ copy.__doc__ = (dct.get('doc_prefix', '')
+ + original.__doc__
+ + dct.get('doc_suffix', ''))
dct[attr] = copy
return type.__new__(mcs, name, bases, dct)
class Implementing(BaseTest):
- doc_filter = lambda x: x.startswith('test')
+ doc_filter = lambda x: x.startswith('test') # noqa: E731
doc_prefix = 'prefix'
doc_suffix = 'suffix'
class OnlyPrefix(BaseTest):
- doc_filter = lambda x: x.startswith('test')
+ doc_filter = lambda x: x.startswith('test') # noqa: E731
doc_prefix = 'prefix'
class OnlySuffix(BaseTest):
- doc_filter = lambda x: x.startswith('test')
+ doc_filter = lambda x: x.startswith('test') # noqa: E731
doc_suffix = 'suffix'
assert (Implementing.testBase.__doc__ == 'prefixbasesuffix')
- assert (Implementing.testWithoutDoc.__doc__ == None)
+ assert (Implementing.testWithoutDoc.__doc__ is None)
assert (OnlySuffix.testBase.__doc__ == 'basesuffix')
- assert (OnlySuffix.testWithoutDoc.__doc__ == None)
+ assert (OnlySuffix.testWithoutDoc.__doc__ is None)
assert (OnlyPrefix.testBase.__doc__ == 'prefixbase')
- assert (OnlyPrefix.testWithoutDoc.__doc__ == None)
+ assert (OnlyPrefix.testWithoutDoc.__doc__ is None)
if _.endswith((".QtCore", ".QtGui", ".QtWidgets")))[-1]]
found = module.__name__.rsplit(".")[-1]
cls = getattr(module, {"QtWidgets": "QApplication",
- "QtGui": "QGuiApplication",
- "QtCore": "QCoreApplication"}[found])
+ "QtGui": "QGuiApplication",
+ "QtCore": "QCoreApplication"}[found])
# Simple way of making instance a singleton
super().setUp()
self.app = cls.instance() or cls([])
+++ /dev/null
-#!/usr/bin/env python
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-from __future__ import annotations
-
-import os
-import time
-
-
-class TimeoutException(Exception):
- def __init__(self, msg):
- self.msg = msg
-
- def __str__(self):
- return repr(self.msg)
-
-
-class ProcessTimer:
- '''Timeout function for controlling a subprocess.Popen instance.
-
- Naive implementation using busy loop, see later other means
- of doing this.
- '''
-
- def __init__(self, proc, timeout):
- self.proc = proc
- self.timeout = timeout
-
- def waitfor(self):
- time_passed = 0
- while (self.proc.poll() is None and time_passed < self.timeout):
- time_passed = time_passed + 1
- time.sleep(1)
-
- if time_passed >= self.timeout:
- raise TimeoutException("Timeout expired, possible deadlock")
-
-
-if __name__ == "__main__":
- # simple example
-
- from subprocess import Popen
-
- proc = Popen(['sleep', '10'])
- t = ProcessTimer(proc, 5)
- try:
- t.waitfor()
- except TimeoutException:
- print(f"timeout - PID: {t.proc.pid}")
- #TODO: detect SO and kill accordingly
- #Linux
- os.kill(t.proc.pid, 9)
- #Windows (not tested)
- #subprocess.Popen("taskkill /F /T /PID %i"%handle.pid , shell=True)
- print(f"exit code: {t.proc.poll()}")
+++ /dev/null
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-from __future__ import annotations
-
-'Tests for processtimer.py'
-
-import unittest
-import os
-
-from subprocess import Popen, PIPE
-from processtimer import TimeoutException, ProcessTimer
-
-
-class TimeoutTest(unittest.TestCase):
-
- def tearDown(self):
- try:
- os.kill(self.proc.pid, 9)
- except OSError:
- pass
-
- def testRaise(self):
- self.proc = Popen(['python2.5', '-c', 'while True: pass'], stdout=PIPE, stderr=PIPE)
- timer = ProcessTimer(self.proc, 1)
- self.assertRaises(TimeoutException, timer.waitfor)
-
-
-class SimpleTest(unittest.TestCase):
-
- def tearDown(self):
- try:
- os.kill(self.proc.pid, 9)
- except OSError:
- pass
-
- def testSimple(self):
- self.proc = Popen(['python2.5', '-c', '"print"'], stdout=PIPE, stderr=PIPE)
- timer = ProcessTimer(self.proc, 10)
- timer.waitfor()
-
-
-class TestEchoOutput(unittest.TestCase):
-
- def tearDown(self):
- try:
- os.kill(self.proc.pid, 9)
- except OSError:
- pass
-
- def testOutput(self):
- self.proc = Popen(['python2.5', '-c', 'print 1',], stdout=PIPE, stderr=PIPE)
- timer = ProcessTimer(self.proc, 1)
- timer.waitfor()
- self.assertEqual(self.proc.stdout.read().strip(), '1')
-
-
-class TestRetCode(unittest.TestCase):
-
- def tearDown(self):
- try:
- os.kill(self.proc.pid, 9)
- except OSError:
- pass
-
- def testSimple(self):
- self.proc = Popen(['python2.5', '-c', 'print 1'], stdout=PIPE, stderr=PIPE)
- timer = ProcessTimer(self.proc, 10)
- timer.waitfor()
-
- self.assertEqual(self.proc.poll(), 0)
-
-
-if __name__ == '__main__':
- unittest.main()
set(shiboken_MAJOR_VERSION "6")
-set(shiboken_MINOR_VERSION "8")
-set(shiboken_MICRO_VERSION "2.1")
+set(shiboken_MINOR_VERSION "9")
+set(shiboken_MICRO_VERSION "2")
set(shiboken_PRE_RELEASE_VERSION_TYPE "")
set(shiboken_PRE_RELEASE_VERSION "")
documentation.cpp documentation.h
documentation_enums.h
dotview.cpp dotview.h
+filecache.cpp filecache.h
enclosingclassmixin.cpp enclosingclassmixin.h
enumtypeentry.h
enumvaluetypeentry.h
#include "qtcompat.h"
-#include <QtCore/QDebug>
-#include <QtCore/QSharedData>
+#include <QtCore/qdebug.h>
+#include <QtCore/qshareddata.h>
using namespace Qt::StringLiterals;
#ifndef ABSTRACTMETAARGUMENT_H
#define ABSTRACTMETAARGUMENT_H
-#include <QtCore/QSharedDataPointer>
+#include <QtCore/qshareddata.h>
QT_FORWARD_DECLARE_CLASS(QDebug)
#include "qtcompat.h"
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QFileInfo>
-#include <QtCore/QMetaObject>
-#include <QtCore/QQueue>
-#include <QtCore/QRegularExpression>
-#include <QtCore/QTemporaryFile>
-#include <QtCore/QTextStream>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qqueue.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qtemporaryfile.h>
+#include <QtCore/qtextstream.h>
#include <cstdio>
#include <algorithm>
return d->m_typedefTargetToName;
}
-void AbstractMetaBuilderPrivate::checkFunctionModifications() const
-{
- const auto &entries = TypeDatabase::instance()->entries();
-
- for (auto it = entries.cbegin(), end = entries.cend(); it != end; ++it) {
- TypeEntryCPtr entry = it.value();
- if (!entry)
- continue;
- if (!entry->isComplex() || !entry->generateCode())
- continue;
+// Check whether a function modification can be found in a class, else
+// warn with candidates.
+static void checkModification(const FunctionModification &modification,
+ const AbstractMetaClassPtr &clazz)
- auto centry = std::static_pointer_cast<const ComplexTypeEntry>(entry);
+{
+ const auto &functions = clazz->functions();
- if (!centry->generateCode())
- continue;
+ auto modificationPredicate = [&clazz, &modification](const AbstractMetaFunctionCPtr &f) {
+ return f->implementingClass() == clazz
+ && modification.matches(f->modificationSignatures());
+ };
- FunctionModificationList modifications = centry->functionModifications();
+ const QString &signature = modification.signature();
+ auto it = std::find_if(functions.cbegin(), functions.cend(), modificationPredicate);
+ if (it != functions.cend()) {
+ if ((*it)->isConstant() && signature.endsWith(u')')) // Warn about missing const
+ qCWarning(lcShiboken, "%s", qPrintable(msgModificationConstMismatch(*it, signature)));
+ return;
+ }
- for (const FunctionModification &modification : std::as_const(modifications)) {
- QString signature = modification.signature();
+ const auto name = QStringView{signature}.left(signature.indexOf(u'(')).trimmed();
- QString name = signature.trimmed();
- name.truncate(name.indexOf(u'('));
+ QStringList possibleSignatures;
+ for (const auto &function : functions) {
+ if (!function->isUserAdded() && !function->isUserDeclared()
+ && function->originalName() == name) {
+ possibleSignatures.append(msgModificationCandidates(function));
+ }
+ }
- const auto clazz = AbstractMetaClass::findClass(m_metaClasses, centry);
- if (!clazz)
- continue;
+ const QString msg = msgNoFunctionForModification(clazz, signature,
+ modification.originalSignature(),
+ possibleSignatures, clazz->functions());
+ qCWarning(lcShiboken, "%s", qPrintable(msg));
+}
- bool found = false;
- QStringList possibleSignatures;
- for (const auto &function : clazz->functions()) {
- if (function->implementingClass() == clazz
- && modification.matches(function->modificationSignatures())) {
- found = true;
- break;
- }
+void AbstractMetaBuilderPrivate::checkFunctionModifications() const
+{
+ for (const auto &entry : TypeDatabase::instance()->entries()) {
+ if (!entry->isComplex() || !entry->generateCode())
+ continue;
- if (function->originalName() == name) {
- const QString signatures = function->modificationSignatures().join(u'/');
- possibleSignatures.append(signatures + u" in "_s
- + function->implementingClass()->name());
- }
- }
+ auto centry = std::static_pointer_cast<const ComplexTypeEntry>(entry);
+ const auto clazz = AbstractMetaClass::findClass(m_metaClasses, centry);
+ if (!clazz)
+ continue;
- if (!found) {
- qCWarning(lcShiboken).noquote().nospace()
- << msgNoFunctionForModification(clazz, signature,
- modification.originalSignature(),
- possibleSignatures, clazz->functions());
- }
- }
+ const FunctionModificationList &modifications = centry->functionModifications();
+ for (const FunctionModification &modification : modifications)
+ checkModification(modification, clazz);
}
}
if (function_item->isDeleted())
return;
ArgumentList arguments = function_item->arguments();
- if (arguments.size() >= 1) { // (Class, Hash seed).
+ if (!arguments.isEmpty()) { // (Class, Hash seed).
if (AbstractMetaClassPtr cls = argumentToClass(arguments.at(0), currentClass))
cls->setHashFunction(function_item->name());
}
const ArgumentModelItem &arg = arguments.at(1);
if (AbstractMetaClassPtr cls = argumentToClass(arg, currentClass)) {
if (arg->type().indirections() < 2)
- cls->setToStringCapability(true, int(arg->type().indirections()));
+ cls->setToStringCapability(true, arg->type().indirections());
}
}
}
}
ReportHandler::startProgress("Checked for inconsistencies in class model.");
- for (const auto &cls : std::as_const(m_metaClasses)) {
- AbstractMetaClass::fixFunctions(cls);
-
- if (cls->canAddDefaultConstructor())
- AbstractMetaClass::addDefaultConstructor(cls);
- if (cls->canAddDefaultCopyConstructor())
- AbstractMetaClass::addDefaultCopyConstructor(cls);
-
- const bool avoidProtectedHack = flags.testFlag(ApiExtractorFlag::AvoidProtectedHack);
- const bool vco =
- AbstractMetaClass::determineValueTypeWithCopyConstructorOnly(cls, avoidProtectedHack);
- cls->setValueTypeWithCopyConstructorOnly(vco);
- cls->typeEntry()->setValueTypeWithCopyConstructorOnly(vco);
- }
+ const bool avoidProtectedHack = flags.testFlag(ApiExtractorFlag::AvoidProtectedHack);
+ for (const auto &cls : std::as_const(m_metaClasses))
+ AbstractMetaClass::fixFunctions(cls, avoidProtectedHack);
const auto &allEntries = types->entries();
// Continue populating namespace?
AbstractMetaClassPtr metaClass = AbstractMetaClass::findClass(m_metaClasses, type);
if (!metaClass) {
- metaClass.reset(new AbstractMetaClass);
+ metaClass = std::make_shared<AbstractMetaClass>();
metaClass->setTypeEntry(type);
addAbstractMetaClass(metaClass, namespaceItem.get());
if (auto extendsType = type->extends()) {
TypeEntryPtr typeEntry;
const auto enclosingTypeEntry = enclosing ? enclosing->typeEntry() : TypeEntryCPtr{};
if (enumItem->accessPolicy() == Access::Private) {
- typeEntry.reset(new EnumTypeEntry(enumItem->qualifiedName().constLast(),
- QVersionNumber(0, 0), enclosingTypeEntry));
+ typeEntry = std::make_shared<EnumTypeEntry>(enumItem->qualifiedName().constLast(),
+ QVersionNumber(0, 0), enclosingTypeEntry);
TypeDatabase::instance()->addType(typeEntry);
} else if (enumItem->enumKind() != AnonymousEnum) {
typeEntry = TypeDatabase::instance()->findType(qualifiedName);
// Synthesize a AbstractMetaType which would be found by an
// instantiation.
AbstractMetaType sourceType;
- sourceType.setTypeEntry(metaClass->templateBaseClass()->typeEntry());
- sourceType.setInstantiations(metaClass->templateBaseClassInstantiations());
- sourceType.decideUsagePattern();
- m_typeSystemTypeDefs.append({sourceType, metaClass});
+ TypeEntryCPtr typeEntry;
+ if (auto templateBase = metaClass->templateBaseClass())
+ typeEntry = templateBase->typeEntry();
+ if (typeEntry) {
+ sourceType.setTypeEntry(typeEntry);
+ sourceType.setInstantiations(metaClass->templateBaseClassInstantiations());
+ sourceType.decideUsagePattern();
+ m_typeSystemTypeDefs.append({sourceType, metaClass});
+ } else {
+ qCWarning(lcShiboken, "Cannot find type entry for source of typedef \"%s\".",
+ qPrintable(metaClass->qualifiedCppName()));
+ }
}
}
}
metaField.setEnclosingClass(cls);
TypeInfo fieldType = field->type();
- auto metaType = translateType(fieldType, cls);
+ auto metaType = translateType(fieldType, cls, {}, &rejectReason);
if (!metaType.has_value()) {
- const QString type = TypeInfo::resolveType(fieldType, currentScope()).qualifiedName().join(u"::"_s);
if (cls->typeEntry()->generateCode()) {
- qCWarning(lcShiboken, "%s",
- qPrintable(msgSkippingField(field, cls->name(), type)));
+ const QString signature = qualifiedFieldSignatureWithType(className, field);
+ m_rejectedFields.insert({AbstractMetaBuilder::UnmatchedFieldType,
+ signature, signature, rejectReason});
}
return {};
}
&& !(metaFunction->isPrivate() && metaFunction->functionType() == AbstractMetaFunction::ConstructorFunction)) {
if (metaFunction->isSignal() && metaClass->hasSignal(metaFunction.get()))
- qCWarning(lcShiboken, "%s", qPrintable(msgSignalOverloaded(metaClass,
- metaFunction.get())));
+ ReportHandler::addGeneralMessage(msgSignalOverloaded(metaClass, metaFunction.get()));
if (metaFunction->isConversionOperator())
fixReturnTypeOfConversionOperator(metaFunction);
void AbstractMetaBuilderPrivate::applyFunctionModifications(const AbstractMetaFunctionPtr &func)
{
AbstractMetaFunction& funcRef = *func;
- for (const FunctionModification &mod : func->modifications(func->implementingClass())) {
+ for (const FunctionModification &mod : func->modifications()) {
if (mod.isRenameModifier()) {
func->setOriginalName(func->name());
func->setName(mod.renamedToName());
}
if (!metaArguments.isEmpty())
- fixArgumentNames(metaFunction, metaFunction->modifications(metaClass));
+ fixArgumentNames(metaFunction, functionMods);
return metaFunction;
}
static QString functionSignature(const FunctionModelItem &functionItem)
{
- QStringList args;
+ QString result = functionItem->name() + u'(';
const ArgumentList &arguments = functionItem->arguments();
- for (const ArgumentModelItem &arg : arguments)
- args << arg->type().toString();
- return functionItem->name() + u'(' + args.join(u',') + u')';
+ for (qsizetype i = 0, size = arguments.size(); i < size; ++i) {
+ if (i > 0)
+ result += u',';
+ result += arguments.at(i)->type().toString();
+ }
+ result += u')';
+ if (functionItem->isConstant())
+ result += "const"_L1;
+ return result;
}
static inline QString qualifiedFunctionSignatureWithType(const FunctionModelItem &functionItem,
case CodeModel::AssignmentOperator:
result = AbstractMetaFunction::AssignmentOperatorFunction;
break;
+ case CodeModel::MoveAssignmentOperator:
+ result = AbstractMetaFunction::MoveAssignmentOperatorFunction;
+ break;
+ case CodeModel::OtherAssignmentOperator:
+ result = AbstractMetaFunction::OtherAssignmentOperatorFunction;
+ break;
case CodeModel::CallOperator:
result = AbstractMetaFunction::CallOperator;
break;
// Create the meta type for a view (std::string_view -> std::string)
static AbstractMetaType createViewOnType(const AbstractMetaType &metaType,
- const TypeEntryCPtr &viewOnTypeEntry)
+ const CppTypeEntryCPtr &viewOnTypeEntry)
{
auto result = metaType;
result.setTypeEntry(viewOnTypeEntry);
case CodeModel::CopyConstructor:
currentClass->setHasDeletedCopyConstructor(true);
break;
+ case CodeModel::MoveConstructor:
+ currentClass->setHasDeletedMoveConstructor(true);
+ break;
+ case CodeModel::AssignmentOperator:
+ currentClass->setHasDeletedAssignmentOperator(true);
+ break;
+ case CodeModel::MoveAssignmentOperator:
+ currentClass->setHasDeletedMoveAssignmentOperator(true);
+ break;
default:
break;
}
return {};
}
- const QString &signature = functionSignature(functionItem);
- if (tdb->isFunctionRejected(className, signature, &rejectReason)) {
- rejectFunction(functionItem, currentClass,
- AbstractMetaBuilder::GenerationDisabled, rejectReason);
- if (ReportHandler::isDebug(ReportHandler::MediumDebug)) {
- qCInfo(lcShiboken, "%s::%s was rejected by the type database (%s).",
- qPrintable(className), qPrintable(signature), qPrintable(rejectReason));
+ QStringList signatures{functionSignature(functionItem)};
+ // FIXME PYSIDE-7: "const" was historically not exactly matched, add a non-const
+ // signature for this to work. Remove in PYSIDE-7
+ if (functionItem->isConstant())
+ signatures.append(signatures.constFirst().left(signatures.constFirst().size() - 5));
+ for (qsizetype i = 0, size = signatures.size(); i < size; ++i) {
+ const QString normalized =
+ QString::fromUtf8(QMetaObject::normalizedSignature(signatures.at(i).toUtf8()));
+ if (normalized != signatures.at(i))
+ signatures.append(normalized);
+ }
+
+ for (const auto &signature : std::as_const(signatures)) {
+ if (tdb->isFunctionRejected(className, signature, &rejectReason)) {
+ rejectFunction(functionItem, currentClass,
+ AbstractMetaBuilder::GenerationDisabled, rejectReason);
+ if (ReportHandler::isDebug(ReportHandler::MediumDebug)) {
+ qCInfo(lcShiboken, "%s::%s was rejected by the type database (%s).",
+ qPrintable(className), qPrintable(signature), qPrintable(rejectReason));
+ }
+ return {};
}
- return {};
}
if (functionItem->isFriend())
AbstractMetaFunction::Flags flags;
auto metaFunction = std::make_shared<AbstractMetaFunction>(functionName);
metaFunction->setCppAttributes(cppAttributes);
- const QByteArray cSignature = signature.toUtf8();
- const QString unresolvedSignature =
- QString::fromUtf8(QMetaObject::normalizedSignature(cSignature.constData()));
- metaFunction->setUnresolvedSignature(unresolvedSignature);
+ metaFunction->setUnresolvedSignatures(signatures);
if (functionItem->isHiddenFriend())
flags.setFlag(AbstractMetaFunction::Flag::HiddenFriend);
metaFunction->setSourceLocation(functionItem->sourceLocation());
if (!type.has_value()) {
const QString reason = msgUnmatchedReturnType(functionItem, errorMessage);
const QString signature = qualifiedFunctionSignatureWithType(functionItem, className);
- qCWarning(lcShiboken, "%s",
- qPrintable(msgSkippingFunction(functionItem, signature, reason)));
+ if (functionItem->attributes().testFlag(FunctionAttribute::Abstract)) { // Potential compilation error
+ qCWarning(lcShiboken, "%s",
+ qPrintable(msgSkippingFunction(functionItem, signature, reason)));
+ }
rejectFunction(functionItem, currentClass,
AbstractMetaBuilder::UnmatchedReturnType, reason);
return {};
arguments.removeLast(); // Strip QT6_DECL_NEW_OVERLOAD_TAIL
if (!currentClass || currentClass->typeEntry()->generateCode()) {
const QString signature = qualifiedFunctionSignatureWithType(functionItem, className);
- qCWarning(lcShiboken, "%s",
- qPrintable(msgStrippingQtDisambiguatedArgument(functionItem, signature)));
+ ReportHandler::addGeneralMessage(msgStrippingQtDisambiguatedArgument(functionItem, signature));
}
break;
}
}
const QString reason = msgUnmatchedParameterType(arg, i, errorMessage);
const QString signature = qualifiedFunctionSignatureWithType(functionItem, className);
- qCWarning(lcShiboken, "%s",
- qPrintable(msgSkippingFunction(functionItem, signature, reason)));
+ if (functionItem->attributes().testFlag(FunctionAttribute::Abstract)) { // Potential compilation error
+ qCWarning(lcShiboken, "%s",
+ qPrintable(msgSkippingFunction(functionItem, signature, reason)));
+ }
rejectFunction(functionItem, currentClass,
AbstractMetaBuilder::UnmatchedArgumentType, reason);
return {};
auto metaType = metaTypeO.value();
// Add view substitution for simple view types of function arguments
// std::string_view -> std::string for foo(std::string_view)
- auto viewOnTypeEntry = metaType.typeEntry()->viewOn();
+ CppTypeEntryCPtr viewOnTypeEntry;
+ if (auto type = std::dynamic_pointer_cast<const CppTypeEntry>(metaType.typeEntry()))
+ viewOnTypeEntry = type->viewOn();
if (viewOnTypeEntry != nullptr && metaType.indirections() == 0
&& metaType.arrayElementType() == nullptr
&& (!metaType.hasInstantiations() || metaType.isContainer())) {
AbstractMetaArgumentList &metaArguments = metaFunction->arguments();
const FunctionModificationList functionMods = currentClass
- ? AbstractMetaFunction::findClassModifications(metaFunction.get(), currentClass)
+ ? AbstractMetaFunction::findMemberModifications(metaFunction.get(), currentClass)
: AbstractMetaFunction::findGlobalModifications(metaFunction.get());
applyCachedFunctionModifications(metaFunction, functionMods);
// Resolve entries added by metabuilder (for example, "GLenum") to match
// the signatures for modifications.
- for (qsizetype i = 0, size = types.size(); i < size; ++i) {
- const auto &e = types.at(i);
+ for (auto &e : types) {
if (e->isPrimitive()) {
const auto pte = std::static_pointer_cast<const PrimitiveTypeEntry>(e);
- types[i] = basicReferencedNonBuiltinTypeEntry(pte);
+ e = basicReferencedNonBuiltinTypeEntry(pte);
}
}
static void synthesizeWarning(const AbstractMetaFunctionCPtr &f)
{
- qCWarning(lcShiboken, "Synthesizing \"%s\"...",
- qPrintable(f->classQualifiedSignature()));
+ ReportHandler::addGeneralMessage("Synthesizing \""_L1 + f->classQualifiedSignature()
+ + "\"..."_L1);
}
static AbstractMetaFunctionPtr
fixSmartPointerClass(std::const_pointer_cast<AbstractMetaClass>(smartPointerClass),
ste);
} else {
- qCWarning(lcShiboken, "Synthesizing smart pointer \"%s\"...",
- qPrintable(ste->qualifiedCppName()));
+ ReportHandler::addGeneralMessage("Synthesizing smart pointer \""_L1
+ + ste->qualifiedCppName() + "\"..."_L1);
m_smartPointers.append(createSmartPointerClass(ste, m_metaClasses));
}
}
qsizetype i = d ? d->m_scopes.size() - 1 : -1;
while (i >= 0) {
typeInfo = TypeInfo::resolveType(_typei, d->m_scopes.at(i--));
- if (typeInfo.qualifiedName().join(u"::"_s) != _typei.qualifiedName().join(u"::"_s))
+ if (typeInfo.qualifiedName() != _typei.qualifiedName())
break;
}
arrayType.setArrayElementType(elementType.value());
const QString &arrayElement = typeInfo.arrayElements().at(i);
if (!arrayElement.isEmpty()) {
- bool _ok;
+ bool _ok{};
const qint64 elems = d
? d->findOutValueFromString(arrayElement, _ok)
: arrayElement.toLongLong(&_ok, 0);
// 4. Special case QFlags (include instantiation in name)
if (qualifiedName == u"QFlags") {
- qualifiedName = typeInfo.toString();
+ qualifiedName = typeInfo.qualifiedInstantationName();
typeInfo.clearInstantiations();
}
// For non-type template parameters, create a dummy type entry on the fly
// as is done for classes.
if (!targType.has_value()) {
- const QString value = ti.qualifiedName().join(u"::"_s);
+ const QString value = ti.qualifiedNameString();
if (isNumber(value)) {
auto module = typeSystemTypeEntry(type);
TypeDatabase::instance()->addConstantValueTypeEntry(value, module);
const auto cit = m_classToItem.constFind(klass);
if (cit == m_classToItem.cend())
return {};
- auto *scope = dynamic_cast<const _ScopeModelItem *>(cit.value());
+ const auto *scope = dynamic_cast<const _ScopeModelItem *>(cit.value());
if (!scope)
return {};
if (auto enumValue = scope->findEnumByValue(expr))
bool AbstractMetaBuilderPrivate::isEnum(const FileModelItem &dom, const QStringList& qualified_name)
{
- CodeModelItem item = dom->model()->findItem(qualified_name, dom);
+ CodeModelItem item = CodeModel::findItem(qualified_name, dom);
return item && item->kind() == _EnumModelItem::__node_kind;
}
QString prefix = i > 0 ? QStringList(scope.mid(0, i)).join(u"::"_s) + u"::"_s : QString();
QString completeName = prefix + name;
const TypeInfo parsed = TypeParser::parse(completeName, &errorMessage);
- QString qualifiedName = parsed.qualifiedName().join(u"::"_s);
+ QString qualifiedName = parsed.qualifiedNameString();
if (qualifiedName.isEmpty()) {
qWarning().noquote().nospace() << "Unable to parse type \"" << completeName
<< "\" while looking for template \"" << name << "\": " << errorMessage;
if (returned.hasInstantiations()) {
AbstractMetaTypeList instantiations = returned.instantiations();
- for (qsizetype i = 0; i < instantiations.size(); ++i) {
- auto ins = inheritTemplateType(templateTypes, instantiations.at(i));
+ for (auto &instantiation : instantiations) {
+ auto ins = inheritTemplateType(templateTypes, instantiation);
if (!ins.has_value())
return {};
- instantiations[i] = ins.value();
+ instantiation = ins.value();
}
returned.setInstantiations(instantiations);
}
const AbstractMetaClassCPtr &templateClass,
const TypeInfo &info, QString *errorMessage)
{
- QString typeName = info.qualifiedName().join("::"_L1);
+ QString typeName = info.qualifiedNameString();
TypeDatabase *typeDb = TypeDatabase::instance();
TypeEntryPtr t;
// Check for a non-type template integer parameter, that is, for a base
{AbstractMetaBuilder::RedefinedToNotClass, "Type redefined to not be a class"_ba},
{AbstractMetaBuilder::UnmatchedReturnType, "Unmatched return type"_ba},
{AbstractMetaBuilder::UnmatchedArgumentType, "Unmatched argument type"_ba},
+ {AbstractMetaBuilder::UnmatchedFieldType, "Unmatched field type"_ba},
{AbstractMetaBuilder::UnmatchedOperator, "Unmatched operator"_ba},
{AbstractMetaBuilder::Deprecated, "Deprecated"_ba}
};
template <class MetaClass>
static bool addClassDependency(const QList<std::shared_ptr<MetaClass> > &classList,
const TypeEntryCPtr &typeEntry,
- std::shared_ptr<MetaClass> clazz,
+ const std::shared_ptr<MetaClass> &clazz,
Graph<std::shared_ptr<MetaClass> > *graph)
{
if (!typeEntry->isComplex() || typeEntry == clazz->typeEntry())
return;
}
- int bestMatchLength = 0;
+ qsizetype bestMatchLength = 0;
for (const auto &headerPath : m_headerPaths) {
if (headerPath.size() > bestMatchLength && matchHeader(headerPath, path))
bestMatchLength = headerPath.size();
#include "clangparser/compilersupport.h"
-#include <QtCore/QFileInfoList>
+#include <QtCore/qfileinfo.h>
#include <optional>
RedefinedToNotClass,
UnmatchedArgumentType,
UnmatchedReturnType,
+ UnmatchedFieldType,
UnmatchedOperator,
Deprecated,
NoReason
#include "modifications_typedefs.h"
#include "typesystem_typedefs.h"
-#include <QtCore/QFileInfo>
-#include <QtCore/QList>
-#include <QtCore/QMap>
-#include <QtCore/QMultiHash>
-#include <QtCore/QSet>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qmap.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qset.h>
#include <optional>
#include <set>
#include "qtcompat.h"
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
#include <algorithm>
#include "parser/codemodel_enums.h"
#include "typesystem_typedefs.h"
-#include <QtCore/QSharedDataPointer>
-#include <QtCore/QString>
+#include <QtCore/qshareddata.h>
+#include <QtCore/qstring.h>
#include <optional>
#include "qtcompat.h"
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
#include <algorithm>
#include "typesystem_typedefs.h"
#include "enclosingclassmixin.h"
-#include <QtCore/QSharedDataPointer>
+#include <QtCore/qshareddata.h>
#include <optional>
#include "qtcompat.h"
-#include <QtCore/QDebug>
-#include <QtCore/QRegularExpression>
+#include <QtCore/qdebug.h>
+#include <QtCore/qregularexpression.h>
#include <algorithm>
QString modifiedName(const AbstractMetaFunction *q) const;
int overloadNumber(const AbstractMetaFunction *q) const;
- const FunctionModificationList &modifications(const AbstractMetaFunction *q,
- const AbstractMetaClassCPtr &implementor) const;
+ const FunctionModificationList &globalModifications(const AbstractMetaFunction *q) const;
+ const FunctionModificationList &memberModifications(const AbstractMetaFunction *q,
+ const AbstractMetaClassCPtr &implementor) const;
bool applyTypeModification(const AbstractMetaFunction *q,
const QString &type, int number, QString *errorMessage);
mutable QString m_cachedMinimalSignature;
mutable QString m_cachedSignature;
mutable QString m_cachedModifiedName;
- QString m_unresolvedSignature;
+ QStringList m_unresolvedSignatures;
FunctionTypeEntryPtr m_typeEntry;
AbstractMetaFunction::FunctionType m_functionType = AbstractMetaFunction::NormalFunction;
AbstractMetaClassCPtr m_class;
AbstractMetaClassCPtr m_implementingClass;
AbstractMetaClassCPtr m_declaringClass;
+ AbstractMetaFunctionCPtr m_overridden; /// overridden base function of a virtual
mutable ModificationCache m_modificationCache;
int m_propertySpecIndex = -1;
AbstractMetaArgumentList m_arguments;
bool AbstractMetaFunction::returnsBool() const
{
- if (d->m_type.typeUsagePattern() != AbstractMetaType::PrimitivePattern)
- return false;
- return basicReferencedTypeEntry(d->m_type.typeEntry())->name() == u"bool";
+ return d->m_type.typeUsagePattern() == AbstractMetaType::PrimitivePattern
+ && d->m_type.basicPrimitiveName() == "bool"_L1;
}
bool AbstractMetaFunction::isOperatorBool() const
d->m_class = cls;
}
-bool AbstractMetaFunction::operator<(const AbstractMetaFunction &other) const
-{
- return compareTo(&other) & NameLessThan;
-}
-
-
/*!
Returns a mask of CompareResult describing how this function is
compares to another function
{
CompareResult result;
- // Enclosing class...
- if (ownerClass() == other->ownerClass())
- result |= EqualImplementor;
-
- // Attributes
- if (attributes() == other->attributes() && cppAttributes() == other->cppAttributes())
- result |= EqualAttributes;
-
// Compare types
- if (type().name() == other->type().name())
- result |= EqualReturnType;
+ if (type() == other->type())
+ result.setFlag(EqualReturnType);
// Compare names
- int cmp = originalName().compare(other->originalName());
+ if (originalName() == other->originalName())
+ result.setFlag(EqualName);
+
+ if (isStatic() == other->isStatic())
+ result.setFlag(EqualStatic);
- if (cmp < 0)
- result |= NameLessThan;
- else if (!cmp)
- result |= EqualName;
+ if (isConstant() == other->isConstant())
+ result.setFlag(EqualConst);
+
+ if (isVirtual() == other->isVirtual())
+ result.setFlag(EqualVirtual);
// compare name after modification...
- cmp = modifiedName().compare(other->modifiedName());
- if (!cmp)
- result |= EqualModifiedName;
+ if (modifiedName() == other->modifiedName())
+ result.setFlag(EqualModifiedName);
// Compare arguments...
- AbstractMetaArgumentList minArguments;
- AbstractMetaArgumentList maxArguments;
- if (arguments().size() < other->arguments().size()) {
- minArguments = arguments();
- maxArguments = other->arguments();
- } else {
- minArguments = other->arguments();
- maxArguments = arguments();
- }
-
- const auto minCount = minArguments.size();
- const auto maxCount = maxArguments.size();
- bool same = true;
- for (qsizetype i = 0; i < maxCount; ++i) {
- if (i < minCount) {
- const AbstractMetaArgument &min_arg = minArguments.at(i);
- const AbstractMetaArgument &max_arg = maxArguments.at(i);
- if (min_arg.type().name() != max_arg.type().name()
- && (min_arg.defaultValueExpression().isEmpty() || max_arg.defaultValueExpression().isEmpty())) {
- same = false;
- break;
- }
- } else {
- if (maxArguments.at(i).defaultValueExpression().isEmpty()) {
- same = false;
- break;
+ const auto argumentCount = d->m_arguments.size();
+ const auto &otherArguments = other->arguments();
+ if (argumentCount == otherArguments.size()) {
+ result.setFlag(EqualArgumentCount);
+ bool equals = true;
+ for (qsizetype a = 0; a < argumentCount; ++a) {
+ if ((d->m_arguments.at(a).type() != otherArguments.at(a).type())) {
+ equals = false;
+ if (a < 4)
+ result.setFlag(CompareResultFlag(DifferArgument1 << a));
}
}
+ if (equals)
+ result.setFlag(EqualArguments);
}
- if (same)
- result |= minCount == maxCount ? EqualArguments : EqualDefaultValueOverload;
-
return result;
}
return true;
}
+AbstractMetaFunctionCPtr AbstractMetaFunction::overridden() const
+{
+ return d->m_overridden;
+}
+
+void AbstractMetaFunction::setOverriddden(const AbstractMetaFunctionCPtr &o)
+{
+ d->m_overridden = o;
+}
+
AbstractMetaFunction *AbstractMetaFunction::copy() const
{
auto *cpy = new AbstractMetaFunction;
case AssignmentOperatorFunction:
case MoveAssignmentOperatorFunction:
case AbstractMetaFunction::MoveConstructorFunction:
+ case OtherAssignmentOperatorFunction:
return false;
default:
if (!isWhiteListed())
return result;
}
-QString AbstractMetaFunction::unresolvedSignature() const
+QStringList AbstractMetaFunction::unresolvedSignatures() const
{
- return d->m_unresolvedSignature;
+ return d->m_unresolvedSignatures;
}
-void AbstractMetaFunction::setUnresolvedSignature(const QString &s)
+void AbstractMetaFunction::setUnresolvedSignatures(const QStringList &s)
{
- d->m_unresolvedSignature = s;
+ d->m_unresolvedSignatures = s;
}
bool AbstractMetaFunction::isConstant() const
QStringList AbstractMetaFunction::modificationSignatures() const
{
QStringList result{minimalSignature()};
- if (d->m_unresolvedSignature != result.constFirst())
- result.append(d->m_unresolvedSignature);
+ for (const auto &signature : std::as_const(d->m_unresolvedSignatures)) {
+ if (signature != result.constFirst())
+ result.append(signature);
+ }
return result;
}
return result;
}
-FunctionModificationList AbstractMetaFunction::findClassModifications(const AbstractMetaFunction *f,
- AbstractMetaClassCPtr implementor)
+static void findModificationRecursion(const QStringList &signatures,
+ const AbstractMetaClassCPtr &implementor,
+ const AbstractMetaClassCPtr &stopAt,
+ unsigned level, FunctionModificationList *result)
+{
+ auto mods = implementor->typeEntry()->functionModifications(signatures);
+ for (auto &mod : mods)
+ mod.setInherited(level != 0);
+ result->append(mods);
+ if (implementor->inheritanceDone() // AbstractMetaBuilder phase?
+ && (implementor != stopAt || mods.isEmpty())) {
+ level += 1;
+ for (const auto &base : implementor->baseClasses())
+ findModificationRecursion(signatures, base, stopAt, level, result);
+ }
+}
+
+FunctionModificationList
+AbstractMetaFunction::findMemberModifications(const AbstractMetaFunction *f,
+ const AbstractMetaClassCPtr &implementor)
{
const auto signatures = f->modificationSignatures();
FunctionModificationList mods;
- while (implementor) {
- mods += implementor->typeEntry()->functionModifications(signatures);
- if ((implementor == implementor->baseClass()) ||
- (implementor == f->implementingClass() && !mods.isEmpty())) {
- break;
- }
- implementor = implementor->baseClass();
- }
+ findModificationRecursion(signatures, implementor, f->implementingClass(), 0, &mods);
return mods;
}
}
const FunctionModificationList &
- AbstractMetaFunctionPrivate::modifications(const AbstractMetaFunction *q,
- const AbstractMetaClassCPtr &implementor) const
+ AbstractMetaFunctionPrivate::globalModifications(const AbstractMetaFunction *q) const
{
+ if (m_addedFunction)
+ return m_addedFunction->modifications();
+ if (m_modificationCache.isEmpty())
+ m_modificationCache.append({{}, AbstractMetaFunction::findGlobalModifications(q)});
+ return m_modificationCache.constFirst().modifications;
+}
+
+const FunctionModificationList &
+ AbstractMetaFunctionPrivate::memberModifications(const AbstractMetaFunction *q,
+ const AbstractMetaClassCPtr &implementor) const
+{
+ Q_ASSERT(implementor);
if (m_addedFunction)
return m_addedFunction->modifications();
for (const auto &ce : m_modificationCache) {
if (ce.klass == implementor)
return ce.modifications;
}
- auto modifications = m_class == nullptr
- ? AbstractMetaFunction::findGlobalModifications(q)
- : AbstractMetaFunction::findClassModifications(q, implementor);
+ auto modifications = AbstractMetaFunction::findMemberModifications(q, implementor);
m_modificationCache.append({implementor, modifications});
return m_modificationCache.constLast().modifications;
}
const FunctionModificationList &
- AbstractMetaFunction::modifications(AbstractMetaClassCPtr implementor) const
+ AbstractMetaFunction::modifications(const AbstractMetaClassCPtr &implementor) const
{
- if (!implementor)
- implementor = d->m_class;
- return d->modifications(this, implementor);
+ // Note: m_class might be null here in early stages of AbstractMetaBuilder.
+ // Fully rely on implementor, then.
+ if (implementor)
+ return d->memberModifications(this, implementor);
+ if (d->m_class)
+ return d->memberModifications(this, d->m_class);
+ return d->globalModifications(this);
}
void AbstractMetaFunction::clearModificationsCache()
}
const QString signature = minimalSignature();
debug << "), signature=\"" << signature << '"';
- if (signature != d->m_unresolvedSignature)
- debug << ", unresolvedSignature=\"" << d->m_unresolvedSignature << '"';
+ if (!d->m_unresolvedSignatures.isEmpty())
+ debug << ", unresolvedSignatures=\"" << d->m_unresolvedSignatures << '"';
if (d->m_constant)
debug << " [const]";
if (d->m_reverse)
#include "typesystem_typedefs.h"
#include "parser/codemodel_enums.h"
-#include <QtCore/QMetaObject>
-#include <QtCore/QScopedPointer>
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qscopedpointer.h>
#include <optional>
MoveConstructorFunction,
AssignmentOperatorFunction,
MoveAssignmentOperatorFunction,
+ OtherAssignmentOperatorFunction, // Assign from some other type
DestructorFunction,
NormalFunction,
SignalFunction,
Q_ENUM(ComparisonOperatorType)
enum CompareResultFlag {
- EqualName = 0x00000001,
- EqualArguments = 0x00000002,
- EqualAttributes = 0x00000004,
- EqualImplementor = 0x00000008,
- EqualReturnType = 0x00000010,
- EqualDefaultValueOverload = 0x00000020,
- EqualModifiedName = 0x00000040,
-
- NameLessThan = 0x00001000,
-
- PrettySimilar = EqualName | EqualArguments,
- Equal = 0x0000001f,
- NotEqual = 0x00001000
+ EqualName = 0x0001,
+ EqualModifiedName = 0x0002,
+ EqualVirtual = 0x0004,
+ EqualStatic = 0x0008,
+ EqualConst = 0x0010,
+ EqualReturnType = 0x0020,
+ EqualArgumentCount = 0x0040,
+ EqualArguments = 0x0080,
+ DifferArgument1 = 0x0100, // Argument 1 is different
+ DifferArgument2 = 0x0200,
+ DifferArgument3 = 0x0400,
+ DifferArgument4 = 0x0800,
+ Differ4ArgumentsMask = 0x0f00,
+ EqualAll = 0xffff
};
Q_DECLARE_FLAGS(CompareResult, CompareResultFlag)
Q_FLAG(CompareResultFlag)
OperatorClassArgumentByValue = 0x4, // The removed class argument was passed by value
InheritedFromTemplate = 0x8, // Inherited from a template in metabuilder
HiddenFriend = 0x10,
- PrivateSignal = 0x20 // Private Qt signal (cannot emit from client code)
+ PrivateSignal = 0x20, // Private Qt signal (cannot emit from client code)
+ CovariantReturn = 0x40 // Return type of virtual function differs (eg clone())
};
Q_DECLARE_FLAGS(Flags, Flag)
QString classQualifiedSignature() const;
/// Signature with unresolved typedefs as seen by the code parser
- QString unresolvedSignature() const;
- void setUnresolvedSignature(const QString &);
+ QStringList unresolvedSignatures() const;
+ void setUnresolvedSignatures(const QStringList &);
bool isConstant() const;
void setConstant(bool constant);
CompareResult compareTo(const AbstractMetaFunction *other) const;
bool isConstOverloadOf(const AbstractMetaFunction *other) const;
- bool operator <(const AbstractMetaFunction &a) const;
-
AbstractMetaFunction *copy() const;
QString conversionRule(TypeSystem::Language language, int idx) const;
*/
bool hasSignatureModifications() const;
- const FunctionModificationList &modifications(AbstractMetaClassCPtr implementor = {}) const;
+ const FunctionModificationList &
+ modifications(const AbstractMetaClassCPtr &implementor = {}) const;
void clearModificationsCache();
DocModificationList addedFunctionDocModifications() const;
- static FunctionModificationList findClassModifications(const AbstractMetaFunction *f,
- AbstractMetaClassCPtr implementor);
+ // For AbstractMetaBuilder only.
+ static FunctionModificationList findMemberModifications(const AbstractMetaFunction *f,
+ const AbstractMetaClassCPtr &implementor);
static FunctionModificationList findGlobalModifications(const AbstractMetaFunction *f);
/**
SourceLocation sourceLocation() const;
void setSourceLocation(const SourceLocation &sourceLocation);
+ /// For virtual functions, return the overridden base class function
+ AbstractMetaFunctionCPtr overridden() const;
+ void setOverriddden(const AbstractMetaFunctionCPtr &o);
+
static const char *pythonRichCompareOpCode(ComparisonOperatorType ct);
static const char *cppComparisonOperator(ComparisonOperatorType ct);
#include "qtcompat.h"
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
#include <algorithm>
m_hasPrivateConstructor(false),
m_hasDeletedDefaultConstructor(false),
m_hasDeletedCopyConstructor(false),
+ m_hasDeletedMoveConstructor(false),
+ m_hasDeletedAssigmentOperator(false),
+ m_hasDeletedMoveAssigmentOperator(false),
+ m_isDefaultConstructible(false),
+ m_isCopyConstructible(false),
m_functionsFixed(false),
m_inheritanceDone(false),
m_hasPrivateDestructor(false),
m_hasVirtualDestructor(false),
m_isTypeDef(false),
m_hasToStringCapability(false),
- m_valueTypeWithCopyConstructorOnly(false),
m_hasCachedWrapper(false)
{
}
bool hasConstructors() const;
qsizetype indexOfProperty(const QString &name) const;
+ bool isImplicitlyDefaultConstructible() const;
+ bool isImplicitlyCopyConstructible() const;
+
uint m_hasVirtuals : 1;
uint m_isPolymorphic : 1;
uint m_hasNonpublic : 1;
uint m_hasPrivateConstructor : 1;
uint m_hasDeletedDefaultConstructor : 1;
uint m_hasDeletedCopyConstructor : 1;
+ uint m_hasDeletedMoveConstructor : 1;
+ uint m_hasDeletedAssigmentOperator : 1;
+ uint m_hasDeletedMoveAssigmentOperator : 1;
+ uint m_isDefaultConstructible : 1;
+ uint m_isCopyConstructible : 1;
uint m_functionsFixed : 1;
uint m_inheritanceDone : 1; // m_baseClasses has been populated from m_baseClassNames
uint m_hasPrivateDestructor : 1;
uint m_hasVirtualDestructor : 1;
uint m_isTypeDef : 1;
uint m_hasToStringCapability : 1;
- uint m_valueTypeWithCopyConstructorOnly : 1;
mutable uint m_hasCachedWrapper : 1;
Documentation m_doc;
bool AbstractMetaClass::hasProtectedFields() const
{
- for (const AbstractMetaField &field : d->m_fields) {
- if (field.isProtected())
- return true;
- }
- return false;
+ return std::any_of(d->m_fields.cbegin(), d->m_fields.cend(),
+ [](const AbstractMetaField &f) { return f.isProtected(); });
}
const TypeEntryCList &AbstractMetaClass::templateArguments() const
AbstractMetaFunctionCPtr AbstractMetaClass::copyConstructor() const
{
- for (const auto &f : d->m_functions) {
- if (f->functionType() == AbstractMetaFunction::CopyConstructorFunction)
- return f;
- }
- return {};
+ return queryFirstFunction(d->m_functions, FunctionQueryOption::CopyConstructor);
}
bool AbstractMetaClass::hasCopyConstructor() const
return copyCt && copyCt->isPrivate();
}
+AbstractMetaFunctionCPtr AbstractMetaClass::moveConstructor() const
+{
+ return queryFirstFunction(d->m_functions, FunctionQueryOption::MoveConstructor);
+}
+
+bool AbstractMetaClass::hasMoveConstructor() const
+{
+ return moveConstructor() != nullptr;
+}
+
+bool AbstractMetaClass::hasPrivateMoveConstructor() const
+{
+ const auto moveCt = moveConstructor();
+ return moveCt && moveCt->isPrivate();
+}
+
+AbstractMetaFunctionCPtr AbstractMetaClass::assignmentOperator() const
+{
+ return queryFirstFunction(d->m_functions, FunctionQueryOption::AssignmentOperator);
+}
+
+bool AbstractMetaClass::hasAssignmentOperator() const
+{
+ return assignmentOperator() != nullptr;
+}
+
+AbstractMetaFunctionCPtr AbstractMetaClass::moveAssignmentOperator() const
+{
+ return queryFirstFunction(d->m_functions, FunctionQueryOption::MoveAssignmentOperator);
+}
+
+bool AbstractMetaClass::hasMoveAssignmentOperator() const
+{
+ return moveAssignmentOperator() != nullptr;
+}
+
void AbstractMetaClassPrivate::addConstructor(AbstractMetaFunction::FunctionType t,
Access access,
const AbstractMetaArgumentList &arguments,
d->m_hasDeletedCopyConstructor = value;
}
+bool AbstractMetaClass::hasDeletedMoveConstructor() const
+{
+ return d->m_hasDeletedMoveConstructor;
+}
+
+void AbstractMetaClass::setHasDeletedMoveConstructor(bool value)
+{
+ d->m_hasDeletedMoveConstructor = value;
+}
+
+bool AbstractMetaClass::hasDeletedAssignmentOperator() const
+{
+ return d->m_hasDeletedAssigmentOperator;
+}
+
+void AbstractMetaClass::setHasDeletedAssignmentOperator(bool value)
+{
+ d->m_hasDeletedAssigmentOperator = value;
+}
+
+bool AbstractMetaClass::hasDeletedMoveAssignmentOperator() const
+{
+ return d->m_hasDeletedMoveAssigmentOperator;
+}
+
+void AbstractMetaClass::setHasDeletedMoveAssignmentOperator(bool value)
+{
+ d->m_hasDeletedMoveAssigmentOperator = value;
+}
+
bool AbstractMetaClass::hasPrivateDestructor() const
{
return d->m_hasPrivateDestructor;
bool AbstractMetaClass::isDefaultConstructible() const
{
- // Private constructors are skipped by the builder.
- if (hasDeletedDefaultConstructor() || hasPrivateConstructor())
- return false;
- const AbstractMetaFunctionCList ctors =
- queryFunctions(FunctionQueryOption::Constructors);
- for (const auto &ct : ctors) {
- if (ct->isDefaultConstructor())
- return ct->isPublic();
- }
- return ctors.isEmpty() && isImplicitlyDefaultConstructible();
+ return d->m_isDefaultConstructible;
}
// Non-comprehensive check for default constructible field
&& !(type.indirections() == 0 && type.isConstant()); // no const values
}
-bool AbstractMetaClass::isImplicitlyDefaultConstructible() const
+bool AbstractMetaClassPrivate::isImplicitlyDefaultConstructible() const
{
- return std::all_of(d->m_fields.cbegin(), d->m_fields.cend(),
- defaultConstructibleField)
- && std::all_of(d->m_baseClasses.cbegin(), d->m_baseClasses.cend(),
+ return std::all_of(m_fields.cbegin(), m_fields.cend(),
+ defaultConstructibleField)
+ && std::all_of(m_baseClasses.cbegin(), m_baseClasses.cend(),
[] (const AbstractMetaClassCPtr &c) {
return c->isDefaultConstructible();
});
}
-static bool canAddDefaultConstructorHelper(const AbstractMetaClass *cls)
-{
- return !cls->isNamespace()
- && !cls->hasDeletedDefaultConstructor()
- && !cls->attributes().testFlag(AbstractMetaClass::HasRejectedConstructor)
- && !cls->hasPrivateDestructor();
-}
-
bool AbstractMetaClass::canAddDefaultConstructor() const
{
- return canAddDefaultConstructorHelper(this) && !hasConstructors()
- && !hasPrivateConstructor() && isImplicitlyDefaultConstructible();
+ return !isNamespace()
+ && !hasDeletedDefaultConstructor()
+ && !attributes().testFlag(AbstractMetaClass::HasRejectedConstructor)
+ && !hasPrivateDestructor()
+ && !hasConstructors()
+ && !hasPrivateConstructor() && d->isImplicitlyDefaultConstructible();
}
bool AbstractMetaClass::isCopyConstructible() const
{
- // Private constructors are skipped by the builder.
- if (hasDeletedCopyConstructor() || hasPrivateCopyConstructor())
- return false;
- const AbstractMetaFunctionCList copyCtors =
- queryFunctions(FunctionQueryOption::CopyConstructor);
- return copyCtors.isEmpty()
- ? isImplicitlyCopyConstructible()
- : copyCtors.constFirst()->isPublic();
+ return d->m_isCopyConstructible;
}
-bool AbstractMetaClass::isImplicitlyCopyConstructible() const
+bool AbstractMetaClassPrivate::isImplicitlyCopyConstructible() const
{
// Fields are currently not considered
- return std::all_of(d->m_baseClasses.cbegin(), d->m_baseClasses.cend(),
+ return std::all_of(m_baseClasses.cbegin(), m_baseClasses.cend(),
[] (const AbstractMetaClassCPtr &c) {
return c->isCopyConstructible();
});
bool AbstractMetaClass::canAddDefaultCopyConstructor() const
{
- if (!canAddDefaultConstructorHelper(this)
- || !d->m_typeEntry->isValue() || isAbstract()
- || hasPrivateCopyConstructor() || hasCopyConstructor()) {
- return false;
- }
- return isImplicitlyCopyConstructible();
+ return d->m_typeEntry->isValue()
+ && !isNamespace()
+ && !hasDeletedCopyConstructor() && !hasCopyConstructor()
+ && !hasDeletedAssignmentOperator() && !hasAssignmentOperator()
+ && !hasDeletedMoveConstructor() && !hasMoveConstructor()
+ && !hasDeletedMoveAssignmentOperator() && !hasMoveAssignmentOperator()
+ && !hasPrivateDestructor()
+ && !isAbstract()
+ && d->isImplicitlyCopyConstructible();
}
static bool classHasParentManagement(const AbstractMetaClassCPtr &c)
return false;
}
+ if (query.testFlag(FunctionQueryOption::DefaultConstructor)
+ && (f->functionType() != AbstractMetaFunction::ConstructorFunction
+ || !f->isDefaultConstructor()
+ || f->ownerClass() != f->implementingClass())) {
+ return false;
+ }
+
if (query.testFlag(FunctionQueryOption::CopyConstructor)
&& (!f->isCopyConstructor() || f->ownerClass() != f->implementingClass())) {
return false;
}
+ if (query.testFlag(FunctionQueryOption::MoveConstructor)
+ && (f->functionType() != AbstractMetaFunction::MoveConstructorFunction
+ || f->ownerClass() != f->implementingClass())) {
+ return false;
+ }
+
+ if (query.testFlag(FunctionQueryOption::AssignmentOperator)
+ && (f->functionType() != AbstractMetaFunction::AssignmentOperatorFunction
+ || f->ownerClass() != f->implementingClass())) {
+ return false;
+ }
+
+ if (query.testFlag(FunctionQueryOption::MoveAssignmentOperator)
+ && (f->functionType() != AbstractMetaFunction::MoveAssignmentOperatorFunction
+ || f->ownerClass() != f->implementingClass())) {
+ return false;
+ }
+
// Destructors are never included in the functions of a class currently
/*
if ((query & Destructors) && (!f->isDestructor()
}
}
-static bool addSuperFunction(const AbstractMetaFunctionCPtr &f)
-{
- switch (f->functionType()) {
- case AbstractMetaFunction::ConstructorFunction:
- case AbstractMetaFunction::CopyConstructorFunction:
- case AbstractMetaFunction::MoveConstructorFunction:
- case AbstractMetaFunction::AssignmentOperatorFunction:
- case AbstractMetaFunction::MoveAssignmentOperatorFunction:
- case AbstractMetaFunction::DestructorFunction:
- return false;
- default:
- break;
- }
- return true;
-}
-
// Add constructors imported via "using" from the base classes. This is not
// needed for normal hidden inherited member functions since we generate a
// cast to the base class to call them into binding code.
return f->isSignal();
}
-void AbstractMetaClass::fixFunctions(const AbstractMetaClassPtr &klass)
+void AbstractMetaClass::fixFunctions(const AbstractMetaClassPtr &klass, bool avoidProtectedHack)
{
auto *d = klass->d.data();
if (d->m_functionsFixed)
}
auto superClass = std::const_pointer_cast<AbstractMetaClass>(superClassC);
- AbstractMetaClass::fixFunctions(superClass);
+ AbstractMetaClass::fixFunctions(superClass, avoidProtectedHack);
// Since we always traverse the complete hierarchy we are only
// interrested in what each super class implements, not what
// we may have propagated from their base classes again.
const auto virtuals = superClass->queryFunctions(FunctionQueryOption::VirtualInCppFunctions);
superFuncs += virtuals;
+ // Loop over super functions, comparing them to the class functions to
+ // find cases of function hiding by name. Virtual super functions that
+ // are not reimplemented are cloned into the class for the Python
+ // override code to be generated.
QSet<AbstractMetaFunctionCPtr> funcsToAdd;
for (const auto &sf : std::as_const(superFuncs)) {
if (sf->isModifiedRemoved())
// we generally don't care about private functions, but we have to get the ones that are
// virtual in case they override abstract functions.
- bool add = addSuperFunction(sf);
+ const bool superIsVirtual = sf->isVirtual();
+ bool add = superIsVirtual && !sf->isDestructor();
for (const auto &cf : std::as_const(nonRemovedFuncs)) {
+ const bool isVirtual = cf->isVirtual();
AbstractMetaFunctionPtr f(std::const_pointer_cast<AbstractMetaFunction>(cf));
const AbstractMetaFunction::CompareResult cmp = cf->compareTo(sf.get());
if (cmp & AbstractMetaFunction::EqualModifiedName) {
add = false;
if (cmp & AbstractMetaFunction::EqualArguments) {
- // Set "override" in case it was not spelled out (since it
- // is then not detected by clang parsing).
const auto attributes = cf->cppAttributes();
- if (attributes.testFlag(FunctionAttribute::Virtual)
- && !attributes.testFlag(FunctionAttribute::Override)
- && !attributes.testFlag(FunctionAttribute::Final)) {
- f->setCppAttribute(FunctionAttribute::Override);
+ if (superIsVirtual && isVirtual) {
+ f->setOverriddden(sf);
+ auto flags = f->flags();
+ if (!flags.testFlag(AbstractMetaFunction::Flag::CovariantReturn)
+ && f->type() != sf->type()) {
+ f->setFlags(flags | AbstractMetaFunction::Flag::CovariantReturn);
+ }
+ // Set "override" in case it was not spelled out (since it
+ // is then not detected by clang parsing).
+ if (!attributes.testFlag(FunctionAttribute::Override)
+ && !attributes.testFlag(FunctionAttribute::Final)) {
+ f->setCppAttribute(FunctionAttribute::Override);
+ }
}
if (f->access() != sf->access()) {
}
// Set the class which first declares this function, afawk
- f->setDeclaringClass(sf->declaringClass());
- }
-
- if (cmp & AbstractMetaFunction::EqualDefaultValueOverload) {
- AbstractMetaArgumentList arguments;
- if (f->arguments().size() < sf->arguments().size())
- arguments = sf->arguments();
- else
- arguments = f->arguments();
- //TODO: fix this
- //for (int i=0; i<arguments.size(); ++i)
- // arguments[i]->setDefaultValueExpression("<#>" + QString());
+ if (superIsVirtual == isVirtual)
+ f->setDeclaringClass(sf->declaringClass());
}
-
// Otherwise we have function shadowing and we can
// skip the thing...
} else if (cmp & AbstractMetaFunction::EqualName && !sf->isSignal()) {
for (const auto &f : std::as_const(funcsToAdd)) {
AbstractMetaFunction *copy = f->copy();
(*copy) += AbstractMetaFunction::AddedMethod;
+ if (f->isVirtual())
+ copy->setOverriddden(f);
funcs.append(AbstractMetaFunctionCPtr(copy));
}
}
addExtraIncludesForFunction(klass, func);
}
+ d->setFunctions(funcs, klass);
+
+ if (!klass->isNamespace())
+ fixSpecialFunctions(klass, avoidProtectedHack);
+
if (hasPrivateConstructors && !hasPublicConstructors) {
(*klass) += AbstractMetaClass::Abstract;
(*klass) -= AbstractMetaClass::FinalInTargetLang;
}
+}
- d->setFunctions(funcs, klass);
+void AbstractMetaClass::fixSpecialFunctions(const AbstractMetaClassPtr &klass,
+ bool avoidProtectedHack)
+{
+ auto *d = klass->d.data();
+ auto typeEntry = klass->typeEntry();
+ // Add implicit default constructor/copy constructor since they
+ // are needed by the generators in the function overload lists.
+ auto ct = AbstractMetaClass::queryFirstFunction(klass->functions(),
+ FunctionQueryOption::DefaultConstructor);
+ if (ct && ct->isPublic()) {
+ d->m_isDefaultConstructible = 1;
+ } else if (klass->canAddDefaultConstructor()) {
+ d->m_isDefaultConstructible = 1;
+ AbstractMetaClass::addDefaultConstructor(klass);
+ }
+
+ // Legacy: For the effective default constructibility of values, we apply a
+ // relaxed criterion: If a visible constructor is there, we assume
+ // minimalConstructorExpression() can guess default parameter values.
+ // FIXME PYSIDE 7: Remove protected handling?
+ bool typeSystemDefaultConstructible = d->m_isDefaultConstructible;
+ if (!typeSystemDefaultConstructible && typeEntry->isValue()) {
+ const auto flags = FunctionQueryOption::Constructors | FunctionQueryOption::Visible;
+ if (auto ct = AbstractMetaClass::queryFirstFunction(d->m_functions, flags)) {
+ typeSystemDefaultConstructible =
+ ct->isPublic() || (ct->isProtected() && !avoidProtectedHack);
+ }
+ }
+ typeEntry->setDefaultConstructibleDetected(typeSystemDefaultConstructible);
+
+ ct = AbstractMetaClass::queryFirstFunction(klass->functions(),
+ FunctionQueryOption::CopyConstructor);
+ if (ct && ct->isPublic()) {
+ d->m_isCopyConstructible = 1;
+ } else if (klass->canAddDefaultCopyConstructor()) {
+ d->m_isCopyConstructible = 1;
+ AbstractMetaClass::addDefaultCopyConstructor(klass);
+ }
+ typeEntry->setCopyableDetected(d->m_isCopyConstructible);
}
bool AbstractMetaClass::needsInheritanceSetup() const
return d->m_typeEntry->isObject();
}
-bool AbstractMetaClass::isCopyable() const
-{
- if (isNamespace() || d->m_typeEntry->isObject())
- return false;
- auto copyable = d->m_typeEntry->copyable();
- return copyable == ComplexTypeEntry::CopyableSet
- || (copyable == ComplexTypeEntry::Unknown && isCopyConstructible());
-}
-
-bool AbstractMetaClass::isValueTypeWithCopyConstructorOnly() const
-{
- return d->m_valueTypeWithCopyConstructorOnly;
-}
-
-void AbstractMetaClass::setValueTypeWithCopyConstructorOnly(bool v)
-{
- d->m_valueTypeWithCopyConstructorOnly = v;
-}
-
-bool AbstractMetaClass::determineValueTypeWithCopyConstructorOnly(const AbstractMetaClassCPtr &c,
- bool avoidProtectedHack)
-{
-
- if (!c->typeEntry()->isValue())
- return false;
- if (c->attributes().testFlag(AbstractMetaClass::HasRejectedDefaultConstructor))
- return false;
- const auto ctors = c->queryFunctions(FunctionQueryOption::AnyConstructor);
- bool copyConstructorFound = false;
- for (const auto &ctor : ctors) {
- switch (ctor->functionType()) {
- case AbstractMetaFunction::ConstructorFunction:
- if (!ctor->isPrivate() && (ctor->isPublic() || !avoidProtectedHack))
- return false;
- break;
- case AbstractMetaFunction::CopyConstructorFunction:
- copyConstructorFound = true;
- break;
- case AbstractMetaFunction::MoveConstructorFunction:
- break;
- default:
- Q_ASSERT(false);
- break;
- }
- }
- return copyConstructorFound;
-}
-
#ifndef QT_NO_DEBUG_STREAM
void AbstractMetaClass::format(QDebug &debug) const
if (attributes().testFlag(AbstractMetaClass::Deprecated))
debug << " [deprecated]";
+ if (d->m_isDefaultConstructible)
+ debug << " [default constructible]";
+ if (d->m_isCopyConstructible)
+ debug << " [copy constructible]";
if (d->m_hasPrivateConstructor)
debug << " [private constructor]";
if (d->m_hasDeletedDefaultConstructor)
debug << " [protected destructor]";
if (d->m_hasVirtualDestructor)
debug << " [virtual destructor]";
- if (d->m_valueTypeWithCopyConstructorOnly)
- debug << " [value type with copy constructor only]";
-
+ if (d->m_hasDeletedMoveConstructor)
+ debug << " [deleted move constructor]";
+ if (d->m_hasDeletedAssigmentOperator)
+ debug << " [deleted assignment]";
+ if (d->m_hasDeletedMoveAssigmentOperator)
+ debug << " [deleted move assignment]";
if (!d->m_baseClasses.isEmpty()) {
debug << ", inherits ";
for (const auto &b : d->m_baseClasses)
#include "typesystem_typedefs.h"
#include <QtCore/qobjectdefs.h>
-#include <QtCore/QScopedPointer>
-#include <QtCore/QStringList>
+#include <QtCore/qscopedpointer.h>
+#include <QtCore/qstringlist.h>
QT_FORWARD_DECLARE_CLASS(QDebug)
AbstractMetaFunctionCPtr copyConstructor() const;
bool hasCopyConstructor() const;
bool hasPrivateCopyConstructor() const;
+ AbstractMetaFunctionCPtr moveConstructor() const;
+ bool hasMoveConstructor() const;
+ bool hasPrivateMoveConstructor() const;
+ AbstractMetaFunctionCPtr assignmentOperator() const;
+ bool hasAssignmentOperator() const;
+ AbstractMetaFunctionCPtr moveAssignmentOperator() const;
+ bool hasMoveAssignmentOperator() const;
static void addDefaultConstructor(const AbstractMetaClassPtr &klass);
static void addDefaultCopyConstructor(const AbstractMetaClassPtr &klass);
bool hasDeletedCopyConstructor() const;
void setHasDeletedCopyConstructor(bool value);
+ bool hasDeletedMoveConstructor() const;
+ void setHasDeletedMoveConstructor(bool value);
+
+ bool hasDeletedAssignmentOperator() const;
+ void setHasDeletedAssignmentOperator(bool value);
+
+ bool hasDeletedMoveAssignmentOperator() const;
+ void setHasDeletedMoveAssignmentOperator(bool value);
+
bool hasPrivateDestructor() const;
void setHasPrivateDestructor(bool value);
bool hasVirtualDestructor() const;
void setHasVirtualDestructor(bool value);
+ // Values detected by the code model (might be overridden by type system).
bool isDefaultConstructible() const;
- bool isImplicitlyDefaultConstructible() const;
- bool canAddDefaultConstructor() const;
-
bool isCopyConstructible() const;
- bool isImplicitlyCopyConstructible() const;
- bool canAddDefaultCopyConstructor() const;
static void addSynthesizedComparisonOperators(const AbstractMetaClassPtr &c);
// Query functions for generators
bool isObjectType() const;
- bool isCopyable() const;
- bool isValueTypeWithCopyConstructorOnly() const;
- void setValueTypeWithCopyConstructorOnly(bool v);
- static bool determineValueTypeWithCopyConstructorOnly(const AbstractMetaClassCPtr &c,
- bool avoidProtectedHack);
static AbstractMetaClassPtr findClass(const AbstractMetaClassList &classes,
QAnyStringView name);
void setSourceLocation(const SourceLocation &sourceLocation);
// For AbstractMetaBuilder
- static void fixFunctions(const AbstractMetaClassPtr &klass);
+ static void fixFunctions(const AbstractMetaClassPtr &klass, bool avoidProtectedHack);
bool needsInheritanceSetup() const;
void setInheritanceDone(bool b);
bool inheritanceDone() const;
void invisibleNamespaceRecursion(Function f) const;
private:
+ bool canAddDefaultConstructor() const;
+ bool canAddDefaultCopyConstructor() const;
+ static void fixSpecialFunctions(const AbstractMetaClassPtr &klass, bool avoidProtectedHack);
+
#ifndef QT_NO_DEBUG_STREAM
void format(QDebug &d) const;
void formatMembers(QDebug &d) const;
#ifndef ABSTRACTMETALANG_ENUMS_H
#define ABSTRACTMETALANG_ENUMS_H
-#include <QtCore/QFlags>
+#include <QtCore/qflags.h>
enum class FunctionQueryOption {
AnyConstructor = 0x0000001, // Any constructor (copy/move)
Constructors = 0x0000002, // Constructors except copy/move
- CopyConstructor = 0x0000004, // Only copy constructors
- //Destructors = 0x0000002, // Only destructors. Not included in class.
- ClassImplements = 0x0000020, // Only functions implemented by the current class
- StaticFunctions = 0x0000080, // Only static functions
- Signals = 0x0000100, // Only signals
- NormalFunctions = 0x0000200, // Only functions that aren't signals
- Visible = 0x0000400, // Only public and protected functions
+ DefaultConstructor = 0x0000004, // Only Default constructors
+ CopyConstructor = 0x0000008, // Only copy constructors
+ MoveConstructor = 0x0000010, // Only move constructors
+ AssignmentOperator = 0x0000020, // Only assignment operator
+ MoveAssignmentOperator = 0x0000040, // Only move assignment operator
+ ClassImplements = 0x0000080, // Only functions implemented by the current class
+ StaticFunctions = 0x0000100, // Only static functions
+ Signals = 0x0000200, // Only signals
+ NormalFunctions = 0x0000400, // Only functions that aren't signals
+ Visible = 0x0000800, // Only public and protected functions
NonStaticFunctions = 0x0004000, // No static functions
Empty = 0x0008000, // Empty overrides of abstract functions
Invisible = 0x0010000, // Only private functions
#ifndef ABSTRACTMETALANG_TYPEDEFS_H
#define ABSTRACTMETALANG_TYPEDEFS_H
-#include <QtCore/QList>
+#include <QtCore/qlist.h>
#include <memory>
#include "containertypeentry.h"
#include "enumtypeentry.h"
#include "flagstypeentry.h"
+#include "cpptypeentry.h"
+#include "primitivetypeentry.h"
#include "qtcompat.h"
#include "typeinfo.h"
#ifndef QT_NO_DEBUG_STREAM
-# include <QtCore/QDebug>
+# include <QtCore/qdebug.h>
#endif
-#include <QtCore/QHash>
-#include <QtCore/QSharedData>
-#include <QtCore/QStack>
+#include <QtCore/qhash.h>
+#include <QtCore/qshareddata.h>
+#include <QtCore/qstack.h>
#include <memory>
AbstractMetaTypeData(const TypeEntryCPtr &t);
int actualIndirections() const;
+ bool passByRef() const;
bool passByConstRef() const;
bool passByValue() const;
AbstractMetaType::TypeUsagePattern determineUsagePattern() const;
return d->m_typeEntry->targetLangEntryName();
}
+QString AbstractMetaType::basicPrimitiveName() const
+{
+ return d->m_typeEntry->isPrimitive()
+ ? basicReferencedTypeEntry(d->m_typeEntry)->name() : name();
+}
+
QString AbstractMetaType::fullName() const
{
return d->m_typeEntry->qualifiedTargetLangName();
void AbstractMetaType::setOriginalTemplateType(const AbstractMetaType &type)
{
if (!d->m_originalTemplateType || *d->m_originalTemplateType != type)
- d->m_originalTemplateType.reset(new AbstractMetaType(type));
+ d->m_originalTemplateType = std::make_shared<AbstractMetaType>(type);
}
const AbstractMetaType *AbstractMetaType::originalTemplateType() const
return result;
}
+bool AbstractMetaTypeData::passByRef() const
+{
+ return m_referenceType == LValueReference && m_indirections.isEmpty();
+}
+
bool AbstractMetaTypeData::passByConstRef() const
{
- return m_constant && m_referenceType == LValueReference && m_indirections.isEmpty();
+ return m_constant && passByRef();
+}
+
+bool AbstractMetaType::passByRef() const
+{
+ return d->passByRef();
}
bool AbstractMetaType::passByConstRef() const
bool AbstractMetaType::useStdMove() const
{
- return (isUniquePointer() && d->passByValue())
+ return (isMoveOnlyType() && d->passByValue())
|| d->m_referenceType == RValueReference;
}
void AbstractMetaType::setArrayElementType(const AbstractMetaType &t)
{
if (!d->m_arrayElementType || *d->m_arrayElementType != t) {
- d->m_arrayElementType.reset(new AbstractMetaType(t));
+ d->m_arrayElementType = std::make_shared<AbstractMetaType>(t);
d->m_signaturesDirty = true;
}
}
if (m_pattern == AbstractMetaType::ArrayPattern) {
// Build nested array dimensions a[2][3] in correct order
result += m_arrayElementType->formatPythonSignature();
- const int arrayPos = result.indexOf(u'[');
+ const auto arrayPos = result.indexOf(u'[');
if (arrayPos != -1)
result.insert(arrayPos, formatArraySize(m_arrayElementCount));
else
}
}
+bool AbstractMetaType::isDefaultConstructible() const
+{
+ auto cppTe = std::dynamic_pointer_cast<const CppTypeEntry>(d->m_typeEntry);
+ return cppTe != nullptr && cppTe->isDefaultConstructible();
+}
+
+bool AbstractMetaType::isCopyable() const
+{
+ auto cppTe = std::dynamic_pointer_cast<const CppTypeEntry>(d->m_typeEntry);
+ return cppTe != nullptr && cppTe->isCopyable();
+}
+
+bool AbstractMetaType::isMovable() const
+{
+ auto cppTe = std::dynamic_pointer_cast<const CppTypeEntry>(d->m_typeEntry);
+ return cppTe != nullptr && cppTe->isMovable();
+}
+
static bool equalsCPtr(const AbstractMetaTypeCPtr &t1, const AbstractMetaTypeCPtr &t2)
{
if (bool(t1) != bool(t2))
bool comparesEqual(const AbstractMetaType &lhs, const AbstractMetaType &rhs) noexcept
{
- return lhs.d->equals(*rhs.d);
+ return lhs.d.constData() == rhs.d.constData() || lhs.d->equals(*rhs.d);
}
bool AbstractMetaType::isEquivalent(const AbstractMetaType &rhs) const
{
- return d->isEquivalent(*rhs.d);
+ return d.constData() == rhs.d.constData() || d->isEquivalent(*rhs.d);
}
const AbstractMetaType *AbstractMetaType::viewOn() const
void AbstractMetaType::setViewOn(const AbstractMetaType &v)
{
if (!d->m_viewOn || *d->m_viewOn != v)
- d->m_viewOn.reset(new AbstractMetaType(v));
+ d->m_viewOn = std::make_shared<AbstractMetaType>(v);
}
AbstractMetaType AbstractMetaType::createVoid()
return d->m_typeEntry->isObject();
}
-bool AbstractMetaType::isUniquePointer() const
-{
- return isSmartPointer() && d->m_typeEntry->isUniquePointer();
-}
-
bool AbstractMetaType::isPointer() const
{
return !d->m_indirections.isEmpty()
bool AbstractMetaType::isValueTypeWithCopyConstructorOnly() const
{
bool result = false;
- if (d->m_typeEntry->isComplex()) {
- const auto cte = std::static_pointer_cast<const ComplexTypeEntry>(d->m_typeEntry);
- result = cte->isValueTypeWithCopyConstructorOnly();
+ if (isValue()) {
+ auto cppTe = std::static_pointer_cast<const CppTypeEntry>(d->m_typeEntry);
+ result = !cppTe->isDefaultConstructible() && cppTe->isCopyable();
}
return result;
}
bool AbstractMetaType::valueTypeWithCopyConstructorOnlyPassed() const
{
- return (passByValue() || passByConstRef())
+ return (passByValue() || passByRef())
&& isValueTypeWithCopyConstructorOnly();
}
#include "parser/codemodel_enums.h"
#include "typedatabase_typedefs.h"
-#include <QtCore/QtCompare>
+#include <QtCore/qcompare.h>
#include <QtCore/qobjectdefs.h>
-#include <QtCore/QHashFunctions>
-#include <QtCore/QSharedDataPointer>
-#include <QtCore/QList>
-#include <QtCore/QSet>
+#include <QtCore/qhashfunctions.h>
+#include <QtCore/qshareddata.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qset.h>
#include <optional>
QString package() const;
QString name() const;
+ /// For a C++ primitive type, resolve the name ("quint32"->"unsigned int")
+ QString basicPrimitiveName() const;
QString fullName() const;
void setTypeUsagePattern(TypeUsagePattern pattern);
// returns true if the type was used as a smart pointer
bool isSmartPointer() const { return typeUsagePattern() == SmartPointerPattern; }
- bool isUniquePointer() const;
// returns true if the type was used as a flag
bool isFlags() const { return typeUsagePattern() == FlagsPattern; }
bool isVolatile() const;
void setVolatile(bool v);
+ // Typesystem specification, potentially overriding the code model detection
+ bool isDefaultConstructible() const;
+ bool isCopyable() const;
+ bool isMovable() const;
+
+ bool isMoveOnlyType() const { return !isCopyable() && isMovable(); }
+
+ bool passByRef() const;
bool passByConstRef() const;
bool passByValue() const;
bool useStdMove() const;
#include "addedfunction_p.h"
#include "typeparser.h"
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
using namespace Qt::StringLiterals;
case ']':
--nestingLevel;
break;
+ default:
+ break;
}
}
return end;
#include "modifications.h"
#include "parser/typeinfo.h"
-#include <QtCore/QList>
-#include <QtCore/QString>
+#include <QtCore/qlist.h>
+#include <QtCore/qstring.h>
#include <memory>
#ifndef ADDEDFUNCTION_P_H
#define ADDEDFUNCTION_P_H
-#include <QtCore/QtCompare>
-#include <QtCore/QList>
-#include <QtCore/QString>
-#include <QtCore/QStringView>
+#include <QtCore/qcompare.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringview.h>
QT_BEGIN_NAMESPACE
class QDebug;
#include "anystringview_helpers.h"
#include <QtCore/QString> // Must go before QAnyStringView for operator<<(QTextStream,QASV)!
-#include <QtCore/QAnyStringView>
-#include <QtCore/QDebug>
-#include <QtCore/QTextStream>
+#include <QtCore/qanystringview.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qtextstream.h>
#include <cstring>
#ifndef ANYSTRINGVIEW_STREAM_H
#define ANYSTRINGVIEW_STREAM_H
-#include <QtCore/QtTypes>
-#include <QtCore/QtClassHelperMacros>
+#include <QtCore/qtypes.h>
+#include <QtCore/qtclasshelpermacros.h>
QT_FORWARD_DECLARE_CLASS(QAnyStringView)
QT_FORWARD_DECLARE_CLASS(QTextStream)
#include "qtcompat.h"
-#include <QtCore/QDir>
-#include <QtCore/QDebug>
-#include <QtCore/QTemporaryFile>
+#include <QtCore/qdir.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qtemporaryfile.h>
#include <algorithm>
#include <iostream>
// sequence of properties will to expand to a sequence of annotations
// annotating nothing, causing clang to complain. Instead, define it away in a
// static assert with the stringified argument in a ','-operator (cf qdoc).
- a->append(QByteArrayLiteral("-DQT_ANNOTATE_CLASS(type,...)=static_assert(sizeof(#__VA_ARGS__),#type);"));
+ a->append("-DQT_ANNOTATE_CLASS(type,...)=static_assert(sizeof(#__VA_ARGS__),#type);"_ba);
// With Qt6, qsimd.h became public header and was included in <QtCore>. That
// introduced a conflict with libclang headers on macOS. To be able to include
// <QtCore>, we prevent its inclusion by adding its include guard.
- a->append(QByteArrayLiteral("-DQSIMD_H"));
+ a->append("-DQSIMD_H"_ba);
}
bool ApiExtractorPrivate::runHelper(ApiExtractorFlags flags)
bool addCompilerSupportArguments = true;
if (clangOptionsSize > 0) {
+ clang::setTargetTriple(m_clangOptions);
qsizetype i = 0;
if (m_clangOptions.at(i) == u"-") {
++i;
std::copy(list.cbegin(), list.cend(), std::back_inserter(*target));
}
+static QString nameFromType(const InstantiatedSmartPointer &ismp)
+{
+ return ismp.type.cppSignature();
+}
+
+static void logInstantiations(QString title, QStringList nameList)
+{
+ if (!nameList.isEmpty()) {
+ std::sort(nameList.begin(), nameList.end());
+ title += " ("_L1 + QString::number(nameList.size()) + u')';
+ const auto underLine = QString(title.size(), u'-');
+ ReportHandler::addGeneralMessage(u'\n' + title + u'\n' + underLine + u'\n');
+ for (const auto &e : nameList)
+ ReportHandler::addGeneralMessage(" * "_L1 + e);
+ ReportHandler::addGeneralMessage(QString(1, u'\n'));
+ }
+}
+
std::optional<ApiExtractorResult> ApiExtractor::run(ApiExtractorFlags flags)
{
if (!d->runHelper(flags))
}
result.m_instantiatedSmartPointers =
topologicalSortSmartPointers(collectContext.instantiatedSmartPointers);
+
+ logInstantiations("Instantiated Containers"_L1, collectContext.instantiatedContainersNames);
+ QStringList smartPointers;
+ std::transform(collectContext.instantiatedSmartPointers.cbegin(),
+ collectContext.instantiatedSmartPointers.cend(),
+ std::back_inserter(smartPointers), nameFromType);
+ logInstantiations("Instantiated Smart Pointers"_L1, smartPointers);
+
+
return result;
}
#include "clangparser/compilersupport.h"
#include "typesystem_typedefs.h"
-#include <QtCore/QFileInfoList>
-#include <QtCore/QStringList>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qstringlist.h>
#include <optional>
#ifndef APIEXTRACTORFLAGS_H
#define APIEXTRACTORFLAGS_H
-#include <QtCore/QFlags>
+#include <QtCore/qflags.h>
enum class ApiExtractorFlag
{
#include "abstractmetalang_typedefs.h"
#include "typesystem_typedefs.h"
-#include <QtCore/QHash>
-#include <QtCore/QMultiHash>
+#include <QtCore/qhash.h>
#include <optional>
#ifndef ARRAYTYPEENTRY_H
#define ARRAYTYPEENTRY_H
-#include "typesystem.h"
+#include "cpptypeentry.h"
class ArrayTypeEntryPrivate;
-class ArrayTypeEntry : public TypeEntry
+class ArrayTypeEntry : public CppTypeEntry
{
public:
explicit ArrayTypeEntry(const TypeEntryCPtr &nested_type, const QVersionNumber &vr,
#include "qtcompat.h"
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QHash>
-#include <QtCore/QMap>
-#include <QtCore/QString>
-#include <QtCore/QStack>
-#include <QtCore/QList>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qmap.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qstack.h>
+#include <QtCore/qlist.h>
using namespace Qt::StringLiterals;
bool visitHeader(const QString &fileName) const;
static const char *specialSystemHeaderReason(SpecialSystemHeader sh);
- void setFileName(const CXCursor &cursor, _CodeModelItem *item);
+ void setFileName(const CXCursor &cursor, _CodeModelItem *item) const;
BaseVisitor *m_baseVisitor;
CodeModel *m_model;
bool BuilderPrivate::addClass(const CXCursor &cursor, CodeModel::ClassType t)
{
QString className = getCursorSpelling(cursor);
- m_currentClass.reset(new _ClassModelItem(m_model, className));
+ m_currentClass = std::make_shared<_ClassModelItem>(m_model, className);
setFileName(cursor, m_currentClass.get());
m_currentClass->setClassType(t);
// Some inner class? Note that it does not need to be (lexically) contained in a
case CXCursor_Destructor:
result = CodeModel::Destructor;
break;
+ case CXCursor_CXXMethod:
+#ifdef CLANG_HAS_ASSIGNMENT_OPERATOR_CHECK
+ if (clang_CXXMethod_isCopyAssignmentOperator(cursor) != 0)
+ result = CodeModel::AssignmentOperator;
+ else if (clang_CXXMethod_isMoveAssignmentOperator(cursor) != 0)
+ result = CodeModel::MoveAssignmentOperator;
+#endif
+ break;
default:
break;
}
void BuilderPrivate::startTemplateTypeAlias(const CXCursor &cursor)
{
const QString target = getCursorSpelling(cursor);
- m_currentTemplateTypeAlias.reset(new _TemplateTypeAliasModelItem(m_model, target));
+ m_currentTemplateTypeAlias = std::make_shared<_TemplateTypeAliasModelItem>(m_model, target);
setFileName(cursor, m_currentTemplateTypeAlias.get());
m_currentTemplateTypeAlias->setScope(m_scope);
}
m_currentClass->addBaseClass({baseClass.first, baseClass.second, access});
}
-void BuilderPrivate::setFileName(const CXCursor &cursor, _CodeModelItem *item)
+void BuilderPrivate::setFileName(const CXCursor &cursor, _CodeModelItem *item) const
{
const SourceRange range = getCursorRange(cursor);
QString file = m_baseVisitor->getFileName(range.first.file);
BaseVisitor::StartTokenResult Builder::startToken(const CXCursor &cursor)
{
+ // Skip inline code
+ if ((cursor.kind >= CXCursor_FirstExpr && cursor.kind <= CXCursor_LastExpr)
+ || (cursor.kind >= CXCursor_FirstStmt && cursor.kind <= CXCursor_LastStmt)) {
+ return Skip;
+ }
+
switch (cursor.kind) {
case CXCursor_CXXAccessSpecifier:
d->m_currentFunctionType = CodeModel::Normal;
#endif
kind = EnumClass;
}
- d->m_currentEnum.reset(new _EnumModelItem(d->m_model, name));
+ d->m_currentEnum = std::make_shared<_EnumModelItem>(d->m_model, name);
d->setFileName(cursor, d->m_currentEnum.get());
d->m_currentEnum->setScope(d->m_scope);
d->m_currentEnum->setEnumKind(kind);
else
enumValue.setUnsignedValue(clang_getEnumConstantDeclUnsignedValue(cursor));
auto enumConstant = std::make_shared<_EnumeratorModelItem>(d->m_model, name);
- enumConstant->setStringValue(d->cursorValueExpression(this, cursor));
+ enumConstant->setStringValue(BuilderPrivate::cursorValueExpression(this, cursor));
enumConstant->setValue(enumValue);
if (clang_getCursorAvailability(cursor) == CXAvailability_Deprecated)
enumConstant->setDeprecated(true);
case CXCursor_FriendDecl:
d->m_withinFriendDecl = true;
break;
- case CXCursor_CompoundStmt: // Function bodies
- return Skip;
case CXCursor_Constructor:
case CXCursor_Destructor: // Note: Also use clang_CXXConstructor_is..Constructor?
case CXCursor_CXXMethod:
// Treat namespaces separately to allow for extending namespaces
// in subsequent modules.
NamespaceModelItem namespaceItem = parentNamespaceItem->findNamespace(name);
- namespaceItem.reset(new _NamespaceModelItem(d->m_model, name));
+ namespaceItem = std::make_shared<_NamespaceModelItem>(d->m_model, name);
d->setFileName(cursor, namespaceItem.get());
namespaceItem->setScope(d->m_scope);
namespaceItem->setType(type);
// and function pointer typedefs.
if (!d->m_currentArgument && d->m_currentFunction) {
const QString name = getCursorSpelling(cursor);
- d->m_currentArgument.reset(new _ArgumentModelItem(d->m_model, name));
+ d->m_currentArgument = std::make_shared<_ArgumentModelItem>(d->m_model, name);
const auto type = clang_getCursorType(cursor);
d->m_currentArgument->setScopeResolution(hasScopeResolution(type));
d->m_currentArgument->setType(d->createTypeInfo(type));
d->m_currentFunction->addArgument(d->m_currentArgument);
- QString defaultValueExpression = d->cursorValueExpression(this, cursor);
+ QString defaultValueExpression = BuilderPrivate::cursorValueExpression(this, cursor);
if (!defaultValueExpression.isEmpty()) {
d->m_currentArgument->setDefaultValueExpression(defaultValueExpression);
d->m_currentArgument->setDefaultValue(true);
#include <codemodel_fwd.h>
+
+#if CINDEX_VERSION_MAJOR > 0 || CINDEX_VERSION_MINOR >= 63 // Clang 16
+# define CLANG_HAS_ASSIGNMENT_OPERATOR_CHECK
+#endif
+
namespace clang {
class BuilderPrivate;
#include "clangdebugutils.h"
#include "clangutils.h"
-#include <QtCore/QDebug>
-#include <QtCore/QString>
+#include <QtCore/qdebug.h>
+#include <QtCore/qstring.h>
#ifndef QT_NO_DEBUG_STREAM
#include "clangutils.h"
#include "clangdebugutils.h"
#include "compilersupport.h"
+#include "reporthandler.h"
-#include <QtCore/QByteArrayList>
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QScopedArrayPointer>
-#include <QtCore/QString>
+#include <QtCore/qbytearraylist.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qscopedpointer.h>
+#include <QtCore/qstring.h>
using namespace Qt::StringLiterals;
std::string_view SourceFileCache::getCodeSnippet(const CXCursor &cursor,
QString *errorMessage)
{
- static const char empty[] = "";
-
if (errorMessage)
errorMessage->clear();
// Quick check for equal locations: Frequently happens if the code is
// the result of a macro expansion
if (range.first == range.second)
- return std::string_view(empty, 0);
+ return {};
if (range.first.file != range.second.file) {
if (errorMessage)
*errorMessage = "Range spans several files"_L1;
- return std::string_view(empty, 0);
+ return {};
}
auto it = m_fileBufferCache.find(range.first.file);
if (fileName.isEmpty()) {
if (errorMessage)
*errorMessage = "Range has no file"_L1;
- return std::string_view(empty, 0);
+ return {};
}
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
str << "Cannot open \"" << QDir::toNativeSeparators(fileName)
<< "\": " << file.errorString();
}
- return std::string_view(empty, 0);
+ return {};
}
it = m_fileBufferCache.insert(range.first.file, file.readAll());
}
<< QDir::toNativeSeparators(getFileName(range.first.file))
<< "\" (" << contents.size() << ')';
}
- return std::string_view(empty, 0);
+ return {};
}
return std::string_view(contents.constData() + pos, end - pos);
clangArgs += detectVulkan();
clangArgs += args;
QScopedArrayPointer<const char *> argv(byteArrayListToFlatArgV(clangArgs));
- qDebug().noquote().nospace() << msgCreateTranslationUnit(clangArgs, flags);
+ ReportHandler::addGeneralMessage(QString::fromUtf8(msgCreateTranslationUnit(clangArgs, flags)));
CXTranslationUnit tu{};
CXErrorCode err = clang_parseTranslationUnit2(index, nullptr, argv.data(),
return tu;
}
+static void setupTarget(CXTranslationUnit translationUnit)
+{
+ const CXTargetInfo targetInfo = clang_getTranslationUnitTargetInfo(translationUnit);
+ const auto tripleCS = clang_TargetInfo_getTriple(targetInfo);
+ clang::setPointerSize(clang_TargetInfo_getPointerWidth(targetInfo));
+ clang::setTargetTriple(QString::fromUtf8(clang_getCString(tripleCS)));
+ clang_disposeString(tripleCS);
+
+ QString message;
+ {
+ QTextStream str(&message);
+ str << "CLANG v" << CINDEX_VERSION_MAJOR << '.' << CINDEX_VERSION_MINOR
+ << " targeting \"" << targetTriple() << "\", " << pointerSize() << "bit.";
+ }
+ qCInfo(lcShiboken, "%s", qPrintable(message));
+ ReportHandler::addGeneralMessage(message + u'\n');
+}
+
/* clangFlags are flags to clang_parseTranslationUnit2() such as
* CXTranslationUnit_KeepGoing (from CINDEX_VERSION_MAJOR/CINDEX_VERSION_MINOR 0.35)
*/
if (!translationUnit)
return false;
+ setupTarget(translationUnit);
+
CXCursor rootCursor = clang_getTranslationUnitCursor(translationUnit);
clang_visitChildren(rootCursor, visitorCallback, reinterpret_cast<CXClientData>(&bv));
#include <clang-c/Index.h>
-#include <QtCore/QByteArrayList>
-#include <QtCore/QHash>
-#include <QtCore/QString>
-#include <QtCore/QList>
+#include <QtCore/qbytearraylist.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qlist.h>
#include <string_view>
#include <utility>
#include "clangutils.h"
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QHashFunctions>
-#include <QtCore/QProcess>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qhashfunctions.h>
+#include <QtCore/qprocess.h>
#include <string_view>
#define CLANGUTILS_H
#include <clang-c/Index.h>
-#include <QtCore/QString>
-#include <QtCore/QStringList>
-#include <QtCore/QtCompare>
-#include <QtCore/QList>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qcompare.h>
+#include <QtCore/qlist.h>
#include <functional>
#include <utility>
#include "qtcompat.h"
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QFileInfo>
-#include <QtCore/QProcess>
-#include <QtCore/QStandardPaths>
-#include <QtCore/QStringList>
-#include <QtCore/QVersionNumber>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qprocess.h>
+#include <QtCore/qstandardpaths.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qversionnumber.h>
#include <clang-c/Index.h>
QString _compilerPath; // Pre-defined compiler path (from command line)
+static unsigned _pointerSize = QT_POINTER_SIZE * 8;
+static QString _targetTriple;
+
const QString &compilerPath()
{
return _compilerPath;
if (homebrewPrefix.isEmpty())
return;
- qCInfo(lcShiboken) << "Found HOMEBREW_OPT with value:" << homebrewPrefix
- << "Assuming homebrew build environment.";
+ ReportHandler::addGeneralMessage("Found HOMEBREW_OPT with value:"_L1
+ + QString::fromUtf8(homebrewPrefix)
+ + "\nAssuming homebrew build environment."_L1);
HeaderPaths::iterator it = headerPaths.begin();
while (it != headerPaths.end()) {
if (it->path.startsWith(homebrewPrefix)) {
- qCInfo(lcShiboken) << "Filtering out homebrew include path: "
- << it->path;
+ ReportHandler::addGeneralMessage("Filtering out homebrew include path: "_L1
+ + QString::fromUtf8(it->path));
it = headerPaths.erase(it);
} else {
++it;
const QByteArrayList stdErrLines = stdErr.split('\n');
bool isIncludeDir = false;
- if (ReportHandler::isDebug(ReportHandler::MediumDebug))
- qCInfo(lcShiboken()).noquote().nospace()
- << "gppInternalIncludePaths:\n compiler: " << compiler
- << "\n stdOut: " << stdOut
- << "\n stdErr: " << stdErr;
-
for (const QByteArray &line : stdErrLines) {
if (isIncludeDir) {
if (line.startsWith(QByteArrayLiteral("End of search list"))) {
}
result.append(headerPath);
}
- } else if (line.startsWith(QByteArrayLiteral("#include <...> search starts here"))) {
+ } else if (line.startsWith("#include <...> search starts here"_ba)) {
isIncludeDir = true;
}
}
if (platform() == Platform::macOS)
filterHomebrewHeaderPaths(result);
+ QString message;
+ {
+ QTextStream str(&message);
+ str << "gppInternalIncludePaths:\n compiler: " << compiler << '\n';
+ for (const auto &h : result)
+ str << " " << h.path << '\n';
+ if (ReportHandler::isDebug(ReportHandler::MediumDebug))
+ str << " stdOut: " << stdOut << "\n stdErr: " << stdErr;
+ }
+ ReportHandler::addGeneralMessage(message);
+
return result;
}
static const char *vulkanVariables[] = {"VULKAN_SDK", "VK_SDK_PATH"};
for (const char *vulkanVariable : vulkanVariables) {
if (qEnvironmentVariableIsSet(vulkanVariable)) {
- const auto option = QByteArrayLiteral("-isystem")
- + qgetenv(vulkanVariable)
- + QByteArrayLiteral("/include");
+ const auto option = "-isystem"_ba + qgetenv(vulkanVariable) + "/include"_ba;
return {option};
}
}
"(neither by checking the environment variables LLVM_INSTALL_DIR, CLANG_INSTALL_DIR "
" nor running llvm-config). This may lead to parse errors.");
} else {
- qCInfo(lcShiboken, "CLANG v%d.%d, builtins includes directory: %s",
- CINDEX_VERSION_MAJOR, CINDEX_VERSION_MINOR,
- qPrintable(clangBuiltinIncludesDir));
p->append(HeaderPath{QFile::encodeName(clangBuiltinIncludesDir),
HeaderType::System});
+ ReportHandler::addGeneralMessage("CLANG builtins includes directory: "_L1
+ + clangBuiltinIncludesDir);
}
}
return LanguageLevel::Default;
}
+unsigned pointerSize()
+{
+ return _pointerSize;
+}
+
+void setPointerSize(unsigned ps)
+{
+ _pointerSize = ps;
+}
+
+QString targetTriple()
+{
+ return _targetTriple;
+
+}
+void setTargetTriple(const QString &t)
+{
+ _targetTriple = t;
+}
+
+void setTargetTriple(const QStringList &clangOptions)
+{
+ static constexpr auto targetOption = "--target="_L1;
+ auto targetOptionPred = [](const QString &o) { return o.startsWith(targetOption); };
+ const auto it = std::find_if(clangOptions.cbegin(), clangOptions.cend(), targetOptionPred);
+ if (it != clangOptions.cend())
+ _targetTriple = it->sliced(targetOption.size());
+}
+
} // namespace clang
#ifndef COMPILERSUPPORT_H
#define COMPILERSUPPORT_H
-#include <QtCore/QByteArrayList>
+#include <QtCore/qbytearraylist.h>
QT_FORWARD_DECLARE_CLASS(QVersionNumber)
QT_FORWARD_DECLARE_CLASS(QString)
Platform platform();
bool setPlatform(const QString &name);
+
+unsigned pointerSize(); // (bit)
+void setPointerSize(unsigned ps); // Set by parser
+
+QString targetTriple();
+void setTargetTriple(const QStringList &clangOptions); // Set from cmd line before parsing
+void setTargetTriple(const QString &t); // Updated by clang parser while parsing
+
} // namespace clang
#endif // COMPILERSUPPORT_H
#include "messages.h"
#include "debughelpers_p.h"
-#include <QtCore/QDebug>
-#include <QtCore/QBuffer>
-#include <QtCore/QFile>
-#include <QtCore/QXmlStreamReader>
-#include <QtCore/QXmlStreamAttributes>
-#include <QtCore/QXmlStreamWriter>
+#include <QtCore/qdebug.h>
+#include <QtCore/qbuffer.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qxmlstream.h>
#include <algorithm>
#ifndef CLASSDOCUMENTATION_H
#define CLASSDOCUMENTATION_H
-#include <QtCore/QStringList>
+#include <QtCore/qstringlist.h>
#include <optional>
#include "exception.h"
#include "typedatabase.h"
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
using namespace Qt::StringLiterals;
return result;
}
+bool comparesEqual(const TemplateInstance &lhs, const TemplateInstance &rhs) noexcept
+{
+ return lhs.m_name == rhs.m_name && lhs.replaceRules == rhs.replaceRules;
+}
+
// ---------------------- CodeSnipFragment
-QString CodeSnipFragment::code() const
+
+static QString fragmentToCodeHelper(const QString &c)
{
- return m_instance ? m_instance->expandCode() : m_code;
+ return c;
}
-// ---------------------- CodeSnipAbstract
-QString CodeSnipAbstract::code() const
+static QString fragmentToCodeHelper(const TemplateInstance &p)
{
- QString res;
- for (const CodeSnipFragment &codeFrag : codeList)
- res.append(codeFrag.code());
+ return p.expandCode();
+}
- return res;
+static QString fragmentToCode(const CodeSnipFragment &codeFrag)
+{
+ return std::visit([](auto f) { return fragmentToCodeHelper(f); }, codeFrag);
+}
+
+static bool isEmptyFragmentHelper(const QString &c)
+{
+ return c.isEmpty();
+}
+
+static bool isEmptyFragmentHelper(const TemplateInstance &)
+{
+ return false;
+}
+
+static bool isEmptyFragment(const CodeSnipFragment &codeFrag)
+{
+ return std::visit([](auto f) { return isEmptyFragmentHelper(f); }, codeFrag);
+}
+
+static size_t hashHelper(const QString &c, size_t seed) noexcept
+{
+ return qHash(c, seed);
+}
+
+static size_t hashHelper(const TemplateInstance &t, size_t seed) noexcept
+{
+ return qHash(t, seed);
}
+size_t qHash(const CodeSnipFragment &codeFrag, size_t seed) noexcept
+{
+ return std::visit([seed](auto f) { return hashHelper(f, seed); }, codeFrag);
+}
+
+static void formatDebugHelper(QDebug &d, const QString &code)
+{
+ const auto lines = QStringView{code}.split(u'\n');
+ for (qsizetype i = 0, size = lines.size(); i < size; ++i) {
+ if (i)
+ d << "\\n";
+ d << lines.at(i).trimmed();
+ }
+}
+
+static void formatDebugHelper(QDebug &d, const TemplateInstance &t)
+{
+ d << "template=\"" << t.name() << '"';
+}
+
+QDebug operator<<(QDebug d, const CodeSnipFragment &codeFrag)
+{
+ QDebugStateSaver saver(d);
+ d.noquote();
+ d.nospace();
+ std::visit([&d](auto f) { formatDebugHelper(d, f); }, codeFrag);
+ return d;
+}
+
+// ---------------------- CodeSnipAbstract
+
void CodeSnipAbstract::addCode(const QString &code)
{
- codeList.append(CodeSnipFragment(fixSpaces(code)));
+ m_codeList.emplace_back(CodeSnipFragment(fixSpaces(code)));
+}
+
+QString CodeSnipAbstract::code() const
+{
+ QString res;
+ for (const auto &codeFrag : m_codeList)
+ res.append(fragmentToCode(codeFrag));
+ return res;
}
void CodeSnipAbstract::purgeEmptyFragments()
{
- auto end = std::remove_if(codeList.begin(), codeList.end(),
- [](const CodeSnipFragment &f) { return f.isEmpty(); });
- codeList.erase(end, codeList.end());
+ auto end = std::remove_if(m_codeList.begin(), m_codeList.end(), isEmptyFragment);
+ m_codeList.erase(end, m_codeList.end());
}
QRegularExpression CodeSnipAbstract::placeHolderRegex(int index)
return QRegularExpression(u'%' + QString::number(index) + "\\b"_L1);
}
+bool comparesEqual(const CodeSnip &lhs, const CodeSnip &rhs) noexcept
+{
+ return lhs.language == rhs.language && lhs.position == rhs.position
+ && lhs.codeList() == rhs.codeList();
+}
+
void purgeEmptyCodeSnips(QList<CodeSnip> *list)
{
for (auto it = list->begin(); it != list->end(); ) {
#include "codesniphelpers.h"
#include "typesystem_enums.h"
-#include <QtCore/QList>
-#include <QtCore/QHash>
-#include <QtCore/QString>
+#include <QtCore/qlist.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qstring.h>
-#include <memory>
+#include <variant>
+
+QT_FORWARD_DECLARE_CLASS(QDebug)
class TemplateInstance
{
public:
- explicit TemplateInstance(const QString &name) : m_name(name) {}
+ explicit TemplateInstance(QString name) : m_name(std::move(name)) {}
void addReplaceRule(const QString &name, const QString &value)
{
- replaceRules[name] = value;
+ replaceRules.insert(name, value);
}
QString expandCode() const;
}
private:
- const QString m_name;
- QHash<QString, QString> replaceRules;
-};
+ Q_DECLARE_EQUALITY_COMPARABLE(TemplateInstance)
-using TemplateInstancePtr = std::shared_ptr<TemplateInstance>;
-
-class CodeSnipFragment
-{
-public:
- CodeSnipFragment() = default;
- explicit CodeSnipFragment(const QString &code) : m_code(code) {}
- explicit CodeSnipFragment(const TemplateInstancePtr &instance) : m_instance(instance) {}
+ friend bool comparesEqual(const TemplateInstance &lhs, const TemplateInstance &rhs) noexcept;
+ friend size_t qHash(const TemplateInstance &t, size_t seed = 0) noexcept
+ { return qHashMulti(seed, t.m_name, t.replaceRules); }
- bool isEmpty() const { return m_code.isEmpty() && !m_instance; }
+ QString m_name;
+ QHash<QString, QString> replaceRules;
+};
- QString code() const;
+using CodeSnipFragment = std::variant<QString, TemplateInstance>;
- TemplateInstancePtr instance() const { return m_instance; }
+size_t qHash(const CodeSnipFragment &codeFrag, size_t seed = 0) noexcept;
-private:
- QString m_code;
- std::shared_ptr<TemplateInstance> m_instance;
-};
+QDebug operator<<(QDebug d, const CodeSnipFragment &codeFrag);
class CodeSnipAbstract : public CodeSnipHelpers
{
public:
+ using CodeSnipFragments = QList<CodeSnipFragment>;
+
QString code() const;
void addCode(const QString &code);
void addCode(QStringView code) { addCode(code.toString()); }
- void addTemplateInstance(const TemplateInstancePtr &ti)
+ void addTemplateInstance(const TemplateInstance &ti)
{
- codeList.append(CodeSnipFragment(ti));
+ m_codeList.emplace_back(CodeSnipFragment{ti});
}
- bool isEmpty() const { return codeList.isEmpty(); }
+ bool isEmpty() const { return m_codeList.empty(); }
void purgeEmptyFragments();
- QList<CodeSnipFragment> codeList;
+ const CodeSnipFragments &codeList() const { return m_codeList; }
static QRegularExpression placeHolderRegex(int index);
+
+private:
+ CodeSnipFragments m_codeList;
};
class TemplateEntry : public CodeSnipAbstract
{
public:
- explicit TemplateEntry(const QString &name) : m_name(name) {}
-
- QString name() const
- {
- return m_name;
- }
+ const QString &name() const { return m_name; }
+ void setName(const QString &n) { m_name = n ;}
private:
QString m_name;
TypeSystem::Language language = TypeSystem::TargetLangCode;
TypeSystem::CodeSnipPosition position = TypeSystem::CodeSnipPositionAny;
+
+ Q_DECLARE_EQUALITY_COMPARABLE(CodeSnip)
+
+ friend bool comparesEqual(const CodeSnip &lhs, const CodeSnip &rhs) noexcept;
+ friend size_t qHash(const CodeSnip &s, size_t seed = 0) noexcept
+ { return qHashMulti(seed, s.position, s.language, s.codeList()); }
};
/// Purge empty fragments and snippets caused by new line characters in
#include "codesniphelpers.h"
-#include <QtCore/QStringList>
+#include <QtCore/qstringlist.h>
#include <algorithm>
#ifndef CODESNIPHELPERS_H
#define CODESNIPHELPERS_H
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
class CodeSnipHelpers
{
#include "modifications_typedefs.h"
#include "pymethoddefentry.h"
-#include <QtCore/QSet>
+#include <QtCore/qset.h>
class ComplexTypeEntryPrivate;
};
Q_DECLARE_FLAGS(TypeFlags, TypeFlag)
- enum CopyableFlag {
- CopyableSet,
- NonCopyableSet,
- Unknown
- };
-
explicit ComplexTypeEntry(const QString &entryName, Type t, const QVersionNumber &vr,
const TypeEntryCPtr &parent);
bool deleteInMainThread() const;
void setDeleteInMainThread(bool d);
- CopyableFlag copyable() const;
- void setCopyable(CopyableFlag flag);
-
- TypeSystem::QtMetaTypeRegistration qtMetaTypeRegistration() const;
- void setQtMetaTypeRegistration(TypeSystem::QtMetaTypeRegistration r);
-
QString hashFunction() const;
void setHashFunction(const QString &hashFunction);
TypeSystem::AllowThread allowThread() const;
void setAllowThread(TypeSystem::AllowThread allowThread);
- QString defaultConstructor() const;
- void setDefaultConstructor(const QString& defaultConstructor);
- bool hasDefaultConstructor() const;
-
TypeEntry *clone() const override;
void useAsTypedef(const ComplexTypeEntryCPtr &source);
TypeSystem::SnakeCase snakeCase() const;
void setSnakeCase(TypeSystem::SnakeCase sc);
- // Determined by AbstractMetaBuilder from the code model.
- bool isValueTypeWithCopyConstructorOnly() const;
- void setValueTypeWithCopyConstructorOnly(bool v);
-
QString docFile() const;
void setDocFile(const QString &docFile);
#include "conditionalstreamreader.h"
-#include <QtCore/QDebug>
-#include <QtCore/QHash>
+#include <QtCore/qdebug.h>
+#include <QtCore/qhash.h>
using namespace Qt::StringLiterals;
#ifndef CONDITIONALSTREAMREADER_H
#define CONDITIONALSTREAMREADER_H
-#include <QtCore/QXmlStreamReader>
+#include <QtCore/qxmlstream.h>
#include <utility>
#ifndef CONFIGURABLETYPEENTRY_H
#define CONFIGURABLETYPEENTRY_H
-#include "typesystem.h"
+#include "cpptypeentry.h"
class ConfigurableTypeEntryPrivate;
-class ConfigurableTypeEntry : public TypeEntry
+class ConfigurableTypeEntry : public CppTypeEntry
{
public:
explicit ConfigurableTypeEntry(const QString &entryName, Type t,
--- /dev/null
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CPPTYPEENTRY_H
+#define CPPTYPEENTRY_H
+
+#include "typesystem.h"
+#include "typesystem_enums.h"
+
+class CppTypeEntryPrivate;
+
+class CppTypeEntry : public TypeEntry
+{
+public:
+ explicit CppTypeEntry(const QString &entryName, Type t, const QVersionNumber &vr,
+ const TypeEntryCPtr &parent);
+
+ const QString &defaultConstructor() const;
+ void setDefaultConstructor(const QString& defaultConstructor);
+ bool hasDefaultConstructor() const { return !defaultConstructor().isEmpty(); }
+
+ // View on: Type to use for function argument conversion, fex
+ // "std::string_view" -> "std::string" for "foo(std::string_view)".
+ // cf AbstractMetaType::viewOn()
+ CppTypeEntryCPtr viewOn() const;
+ void setViewOn(const CppTypeEntryCPtr &v);
+
+ // Typesystem specification, potentially overriding the code model detection
+ bool isDefaultConstructible() const;
+ bool isCopyable() const;
+ bool isMovable() const;
+ bool isMoveOnlyType() const { return !isCopyable() && isMovable(); }
+
+ // Parser/code model interface
+ TypeSystem::DefaultConstructibleFlag defaultConstructibleFlag() const;
+ void setDefaultConstructibleFlag(TypeSystem::DefaultConstructibleFlag flag);
+ void setDefaultConstructibleDetected(bool c); // set value detected by code model
+
+ TypeSystem::CopyableFlag copyableFlag() const;
+ void setCopyableFlag(TypeSystem::CopyableFlag flag);
+ void setCopyableDetected(bool c); // set value detected by code model
+
+ TypeSystem::MovableFlag movableFlag() const;
+ void setMovableFlag(TypeSystem::MovableFlag flag);
+
+ TypeSystem::QtMetaTypeRegistration qtMetaTypeRegistration() const;
+ void setQtMetaTypeRegistration(TypeSystem::QtMetaTypeRegistration r);
+
+ TypeEntry *clone() const override;
+
+#ifndef QT_NO_DEBUG_STREAM
+ void formatDebug(QDebug &debug) const override;
+#endif
+
+protected:
+ explicit CppTypeEntry(CppTypeEntryPrivate *d);
+};
+
+#endif // CPPTYPEENTRY_H
#include "customconversion_typedefs.h"
#include "typesystem_typedefs.h"
-#include <QtCore/QList>
-#include <QtCore/QString>
+#include <QtCore/qlist.h>
+#include <QtCore/qstring.h>
QT_FORWARD_DECLARE_CLASS(QDebug)
#ifndef CUSTOMCONVERSION_TYPEDEFS_H
#define CUSTOMCONVERSION_TYPEDEFS_H
-#include <QtCore/QList>
+#include <QtCore/qlist.h>
#include <memory>
#ifndef CUSTOMTYPENENTRY_H
#define CUSTOMTYPENENTRY_H
-#include "typesystem.h"
+#include "cpptypeentry.h"
-class CustomTypeEntry : public TypeEntry
+class CustomTypeEntryPrivate;
+
+class CustomTypeEntry : public CppTypeEntry
{
public:
explicit CustomTypeEntry(const QString &entryName, const QVersionNumber &vr,
#endif
protected:
- explicit CustomTypeEntry(TypeEntryPrivate *d);
+ explicit CustomTypeEntry(CustomTypeEntryPrivate *d);
};
#ifndef DEBUGHELPERS_P_H
#define DEBUGHELPERS_P_H
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
#include <memory>
template <class T>
#ifndef DEPENDENCY_H
#define DEPENDENCY_H
-#include <QtCore/QList>
+#include <QtCore/qlist.h>
#include <utility>
#include "complextypeentry.h"
#include "xmlutils.h"
-#include <QtCore/QBuffer>
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QTextStream>
+#include <QtCore/qbuffer.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qtextstream.h>
#include "qtcompat.h"
#include <cstdlib>
-#ifdef HAVE_LIBXSLT
-# include <libxslt/xsltutils.h>
-# include <libxslt/transform.h>
-#endif
#include <algorithm>
#include "modifications_typedefs.h"
#include "documentation.h"
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
#include <memory>
#include "documentation.h"
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
Documentation::Documentation(const QString &detailed,
const QString &brief,
#include "documentation_enums.h"
-#include <QtCore/QString>
-#include <QtCore/QtCompare>
+#include <QtCore/qstring.h>
+#include <QtCore/qcompare.h>
QT_FORWARD_DECLARE_CLASS(QDebug)
#ifndef DOCUMENTATION_ENUMS_H
#define DOCUMENTATION_ENUMS_H
-#include <QtCore/QtTypes>
+#include <QtCore/qtypes.h>
+
+enum class DocumentationTarget : uint8_t
+{
+ Documentation, // Documentation
+ DocString // Python doc string (binding code)
+};
enum class DocumentationFormat : uint8_t
{
#include "dotview.h"
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QProcess>
-#include <QtCore/QTemporaryFile>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qprocess.h>
+#include <QtCore/qtemporaryfile.h>
using namespace Qt::StringLiterals;
#ifndef DOTVIEW_H
#define DOTVIEW_H
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
/// Show a dot digraph in an image viewer
/// \param name base name for files
#include "qtcompat.h"
-#include <QtCore/QFile>
-#include <QtCore/QDir>
+#include <QtCore/qfile.h>
+#include <QtCore/qdir.h>
using namespace Qt::StringLiterals;
#ifndef EXCEPTION_H
#define EXCEPTION_H
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
#include <string>
#include <exception>
--- /dev/null
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "filecache.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qregularexpression.h>
+
+#include <algorithm>
+
+using namespace Qt::StringLiterals;
+
+constexpr qsizetype MAX_CACHE_SIZE = 20;
+
+static QString msgCannotFindSnippet(const QString &file, const QString &snippetLabel)
+{
+ return "Cannot find snippet \""_L1 + snippetLabel + "\" in \""_L1
+ + QDir::toNativeSeparators(file) + "\"."_L1;
+}
+
+static QString msgUnterminatedSnippet(const QString &file, const QString &snippetLabel)
+{
+ return "Snippet \""_L1 + snippetLabel + "\" in \""_L1
+ + QDir::toNativeSeparators(file) + "\" is not terminated."_L1;
+}
+
+static QString msgCannotOpenFileForReading(const QFile &f)
+{
+ return "Failed to open file \""_L1 + QDir::toNativeSeparators(f.fileName())
+ + "\" for reading: "_L1 +f.errorString();
+}
+
+std::optional<QString> FileCache::fileContents(const QString &name)
+{
+ const qsizetype index = ensureEntry(name);
+ if (index == -1)
+ return std::nullopt;
+ return m_cache.at(index).contents;
+}
+
+void FileCache::ensureLines(FileCacheEntry *entry)
+{
+ if (entry->lines.isEmpty())
+ entry->lines = QStringView{entry->contents}.split(u'\n');
+}
+
+std::optional<FileCache::Lines> FileCache::lines(const QString &name)
+{
+ const qsizetype index = ensureEntry(name);
+ if (index == -1)
+ return std::nullopt;
+ FileCacheEntry &entry = m_cache[index];
+ ensureLines(&entry);
+ return entry.lines;
+}
+
+std::optional<QString> FileCache::fileSnippet(const QString &name,
+ const QString &snippetName,
+ const QRegularExpression &snippetPattern)
+{
+ const qsizetype index = ensureEntry(name);
+ if (index == -1)
+ return std::nullopt;
+ FileCacheEntry &entry = m_cache[index];
+ ensureLines(&entry);
+
+ // Check for a comment line and the snippet ID
+ auto pred = [&snippetPattern](QStringView line) {
+ return (line.contains(u'/') || line.contains(u'#'))
+ && snippetPattern.matchView(line).hasMatch(); };
+
+ const auto end = entry.lines.cend();
+ const auto i1 = std::find_if(entry.lines.cbegin(), end, pred);
+ if (i1 == end) {
+ m_error = msgCannotFindSnippet(name, snippetName);
+ return std::nullopt;
+ }
+
+ auto pos = i1;
+ const auto i2 = std::find_if(++pos, end, pred);
+ if (i2 == end) {
+ m_error = msgUnterminatedSnippet(name, snippetName);
+ return std::nullopt;
+ }
+
+ const QChar *startSnippet = i1->constData() + i1->size() + 1;
+ const auto snippetSize = i2->constData() - startSnippet;
+ const auto startSnippetIndex = startSnippet - entry.lines.cbegin()->constData();
+ return entry.contents.sliced(startSnippetIndex, snippetSize);
+}
+
+qsizetype FileCache::ensureEntry(const QString &name)
+{
+ const qsizetype index = indexOf(name);
+ if (index != -1) {
+ ++m_hits;
+ return index;
+ }
+
+ ++m_misses;
+ m_error.clear();
+ QFile file(name);
+ if (!file.open(QIODevice::Text | QIODevice::ReadOnly)) {
+ m_error = msgCannotOpenFileForReading(file);
+ return -1;
+ }
+
+ QString contents = QString::fromUtf8(file.readAll());
+ m_cache.prepend({name, contents, {}});
+ while (m_cache.size() >= MAX_CACHE_SIZE)
+ m_cache.removeLast();
+ return 0;
+}
+
+qsizetype FileCache::indexOf(const QString &name) const
+{
+ for (qsizetype i = 0, size = m_cache.size(); i < size; ++i) {
+ if (m_cache.at(i).name == name)
+ return i;
+ }
+ return -1;
+}
+
+void FileCache::formatDebug(QDebug &debug) const
+{
+ debug << "FileCache(" << m_cache.size() << " entries, "
+ << m_hits << " hits, " << m_misses << " misses [";
+ for (const auto &e : m_cache)
+ debug << QDir::toNativeSeparators(e.name) << ' ' << e.contents.size() << "B ";
+ debug << "])";
+}
+
+QDebug operator<<(QDebug debug, const FileCache &c)
+{
+ QDebugStateSaver saver(debug);
+ debug.noquote();
+ debug.nospace();
+ c.formatDebug(debug);
+ return debug;
+}
--- /dev/null
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef FILECACHE_H
+#define FILECACHE_H
+
+#include <QtCore/qlist.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringview.h>
+
+#include <optional>
+
+QT_FORWARD_DECLARE_CLASS(QRegularExpression)
+QT_FORWARD_DECLARE_CLASS(QDebug)
+
+// Queue-based cache for the contents of a number of recent files with a
+// convenience API for retrieving lines and regexp-delimited snippets.
+class FileCache
+{
+public:
+ using Lines = QList<QStringView>;
+
+ std::optional<QString> fileContents(const QString &name);
+ std::optional<Lines> lines(const QString &name);
+ std::optional<QString> fileSnippet(const QString &name,
+ const QString &snippetName,
+ const QRegularExpression &snippetPattern);
+
+ const QString &errorString() const { return m_error; }
+
+ void formatDebug(QDebug &debug) const;
+
+private:
+ struct FileCacheEntry
+ {
+ QString name;
+ QString contents;
+ Lines lines;
+ };
+
+ qsizetype ensureEntry(const QString &name);
+ qsizetype indexOf(const QString &name) const;
+ static void ensureLines(FileCacheEntry *entry);
+
+ QList<FileCacheEntry> m_cache;
+ QString m_error;
+ int m_hits = 0;
+ int m_misses = 0;
+};
+
+QDebug operator<<(QDebug debug, const FileCache &c);
+
+#endif // FILECACHE_H
#include "reporthandler.h"
#include "exception.h"
-#include <QtCore/QFileInfo>
-#include <QtCore/QDir>
-#include <QtCore/QDebug>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qdebug.h>
#include <cstdio>
#ifndef FLAGSTYPEENTRY_H
#define FLAGSTYPEENTRY_H
-#include "typesystem.h"
+#include "cpptypeentry.h"
class EnumTypeEntry;
class FlagsTypeEntryPrivate;
// FlagsTypeEntry is configurable for global flags only
-class FlagsTypeEntry : public TypeEntry
+class FlagsTypeEntry : public CppTypeEntry
{
public:
explicit FlagsTypeEntry(const QString &entryName, const QVersionNumber &vr,
#include "dotview.h"
-#include <QtCore/QDebug>
-#include <QtCore/QFile>
-#include <QtCore/QHash>
-#include <QtCore/QList>
-#include <QtCore/QString>
-#include <QtCore/QTextStream>
+#include <QtCore/qdebug.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qtextstream.h>
#include <algorithm>
#ifndef HEADER_PATHS_H
#define HEADER_PATHS_H
-#include <QtCore/QByteArray>
-#include <QtCore/QList>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qlist.h>
enum class HeaderType
{
#include "include.h"
#include "textstream.h"
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QHash>
-#include <QtCore/QTextStream>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qtextstream.h>
#include "qtcompat.h"
#ifndef INCLUDE_H
#define INCLUDE_H
-#include <QtCore/QtCompare>
-#include <QtCore/QHashFunctions>
-#include <QtCore/QString>
-#include <QtCore/QList>
+#include <QtCore/qcompare.h>
+#include <QtCore/qhashfunctions.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qlist.h>
QT_BEGIN_NAMESPACE
class QTextStream;
int compare(const Include &rhs) const;
private:
- friend size_t qHash(Include &inc, size_t seed = 0) noexcept
+ friend size_t qHash(const Include &inc, size_t seed = 0) noexcept
{
return qHashMulti(seed, inc.m_type, inc.m_name);
}
#include "qtcompat.h"
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QStringList>
-#include <QtCore/QXmlStreamReader>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qlibraryinfo.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qxmlstream.h>
#include <algorithm>
#include <iterator>
return s;
}
+QString msgModificationCandidates(const AbstractMetaFunctionCPtr &function)
+{
+ QString result;
+ const auto &signatures = function->modificationSignatures();
+ for (qsizetype i = 0, size = signatures.size(); i < size; ++i) {
+ if (i > 0)
+ result += " / "_L1;
+ result += u'"' + signatures.at(i) + u'"';
+ }
+ if (auto klazz = function->implementingClass())
+ result += " in "_L1 + klazz->name();
+ return result;
+}
+
QString msgNoFunctionForModification(const AbstractMetaClassCPtr &klass,
const QString &signature,
const QString &originalSignature,
return result;
}
+QString msgModificationConstMismatch(const AbstractMetaFunctionCPtr &function,
+ const QString &modificationSignature)
+{
+ QString result;
+ QTextStream str(&result);
+ if (auto klazz = function->implementingClass())
+ str << klazz->typeEntry()->sourceLocation();
+ str << "signature \"" << modificationSignature
+ << "\" needs \"const\" to fully match \"" << function->classQualifiedSignature()
+ << "\". The partial matching will be removed in a future release.";
+ return result;
+}
+
QString msgArgumentIndexOutOfRange(const AbstractMetaFunction *func, int index)
{
QString result;
+ u" for "_s + name;
}
-QString msgCannotFindSnippet(const QString &file, const QString &snippetLabel)
-{
- QString result;
- QTextStream str(&result);
- str << "Cannot find snippet \"" << snippetLabel << "\" in "
- << QDir::toNativeSeparators(file) << '.';
- return result;
-}
-
QString msgSnippetError(const QString &context, const char *what)
{
return "Error processing code snippet of "_L1 + context
<< "), tried: " << nativeCandidates.join(", "_L1);
return result;
}
+
+QString msgCannotCall(const AbstractMetaFunctionCPtr &func,
+ int arg, bool injectCodeCallsFunc, bool hasConversionRule)
+{
+ QString result;
+ QTextStream str(&result);
+ str << "No way to generate a binding call for \"" << func->ownerClass()->name() << "::"
+ << func->signature() << '"';
+ if (func->isUserAdded())
+ str << " (user added)";
+ str << " with the modifications for argument " << (arg + 1) << ':';
+ if (!injectCodeCallsFunc)
+ str << " There is no code injection calling the function.";
+ if (!hasConversionRule)
+ str << " There is no conversion rule.";
+ return result;
+}
+
+QString msgRemoveRedundantOverload(const AbstractMetaFunctionCPtr &func,
+ const QString &type)
+{
+ return "Removing \""_L1 + func->classQualifiedSignature()
+ + "\" due to presence of an overload taking a \""_L1
+ + type + "\" parameter."_L1;
+}
+
+QString msgCommandLineArguments(const QStringList &argv)
+{
+ QString result = "Host platform: "_L1 + QLatin1StringView(QLibraryInfo::build())
+ + "\nCommand line:\n "_L1;
+ for (const QString &arg : argv) {
+ result.append(u' ');
+ const bool quote = arg.contains(u' ');
+ if (quote)
+ result.append(u'"');
+ result.append(arg);
+ if (quote)
+ result.append(u'"');
+ }
+ result.append(u'\n');
+ return result;
+}
#include "modifications_typedefs.h"
#include "typesystem_typedefs.h"
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
class EnumTypeEntry;
class FunctionTypeEntry;
QString msgArgumentIndexOutOfRange(const AbstractMetaFunction *func, int index);
+QString msgModificationCandidates(const AbstractMetaFunctionCPtr &function);
+
QString msgNoFunctionForModification(const AbstractMetaClassCPtr &klass,
const QString &signature,
const QString &originalSignature,
const QStringList &possibleSignatures,
const AbstractMetaFunctionCList &allFunctions);
+QString msgModificationConstMismatch(const AbstractMetaFunctionCPtr &function,
+ const QString &modificationSignature);
+
QString msgTypeModificationFailed(const QString &type, int n,
const AbstractMetaFunction *func,
const QString &why);
QString msgCannotFindView(const QString &viewedName, const QString &name);
-QString msgCannotFindSnippet(const QString &file, const QString &snippetLabel);
QString msgSnippetError(const QString &context, const char *what);
QString msgUnableToResolveTypedef(const QString &sourceType, const QString &sourceName);
QString msgCannotFindQDocFile(const AbstractMetaClassCPtr &metaClass,
const QStringList &candidates);
+QString msgCannotCall(const AbstractMetaFunctionCPtr &func,
+ int arg, bool injectCodeCallsFunc, bool hasConversionRule);
+
+QString msgRemoveRedundantOverload(const AbstractMetaFunctionCPtr &func,
+ const QString &type);
+
+QString msgCommandLineArguments(const QStringList &argv);
+
#endif // MESSAGES_H
#include "qtcompat.h"
-#include <QtCore/QDebug>
-#include <QtCore/QRegularExpression>
+#include <QtCore/qdebug.h>
+#include <QtCore/qregularexpression.h>
#include <algorithm>
#include <limits>
QDebugStateSaver saver(d);
d.noquote();
d.nospace();
- const auto size = s.codeList.size();
+ const auto &fragments = s.codeList();
+ const auto size = fragments.size();
d << "CodeSnip(language=" << s.language << ", position=" << s.position
<< ", fragments[" << size << "]=";
for (qsizetype i = 0; i < size; ++i) {
- const auto &f = s.codeList.at(i);
if (i)
d << ", ";
- d << '#' << i << ' ';
- if (!f.instance()) {
- d << '"';
- const QString &code = f.code();
- const auto lines = QStringView{code}.split(u'\n');
- for (qsizetype i = 0, size = lines.size(); i < size; ++i) {
- if (i)
- d << "\\n";
- d << lines.at(i).trimmed();
- }
- d << '"';
- } else {
- d << "template=\"" << f.instance()->name() << '"';
- }
+ d << '#' << i << ' ' << fragments.at(i);
}
d << ')';
return d;
noNullPointers(false), resetAfterUse(false), array(false)
{}
+ bool equals(const ArgumentModificationData &rhs) const noexcept;
+ size_t hash(size_t seed) const noexcept;
+
QList<ReferenceCount> referenceCounts;
QString modified_type;
QString pyiType;
uint array : 1;
};
+bool ArgumentModificationData::equals(const ArgumentModificationData &rhs) const noexcept
+{
+ return referenceCounts == rhs.referenceCounts
+ && modified_type == rhs.modified_type
+ && pyiType == rhs.pyiType
+ && replacedDefaultExpression == rhs.replacedDefaultExpression
+ && m_targetOwnerShip == rhs.m_targetOwnerShip
+ && m_nativeOwnerShip == rhs.m_nativeOwnerShip
+ && conversion_rules == rhs.conversion_rules
+ && owner == rhs.owner
+ && renamed_to == rhs.renamed_to
+ && index == rhs.index
+ && removedDefaultExpression == rhs.removedDefaultExpression
+ && removed == rhs.removedDefaultExpression
+ && noNullPointers == rhs.noNullPointers
+ && resetAfterUse == rhs.resetAfterUse
+ && array == rhs.array;
+}
+
+size_t ArgumentModificationData::hash(size_t seed) const noexcept
+{
+ return qHashMulti(seed, referenceCounts, modified_type, pyiType,replacedDefaultExpression,
+ m_targetOwnerShip, m_nativeOwnerShip, conversion_rules, owner,
+ renamed_to, index, removedDefaultExpression, removed, noNullPointers,
+ resetAfterUse, array);
+}
+
ArgumentModification::ArgumentModification() : d(new ArgumentModificationData)
{
}
d->referenceCounts.append(value);
}
+bool ArgumentModification::equals(const ArgumentModification &rhs) const noexcept
+{
+ return d.constData() == rhs.d.constData() || d->equals(*rhs.d);
+}
+
+size_t ArgumentModification::hash(size_t seed) const noexcept
+{
+ return d->hash(seed);
+}
+
class FunctionModificationData : public QSharedData
{
public:
+ bool equals(const FunctionModificationData &rhs) const noexcept;
+ size_t hash(size_t seed) const noexcept;
+
QString renamedToName;
FunctionModification::Modifiers modifiers;
CodeSnipList m_snips;
QRegularExpression m_signaturePattern;
int m_overloadNumber = TypeSystem::OverloadNumberUnset;
bool removed = false;
+ bool inherited = false;
TypeSystem::AllowThread m_allowThread = TypeSystem::AllowThread::Unspecified;
TypeSystem::ExceptionHandling m_exceptionHandling = TypeSystem::ExceptionHandling::Unspecified;
TypeSystem::SnakeCase snakeCase = TypeSystem::SnakeCase::Unspecified;
};
+bool FunctionModificationData::equals(const FunctionModificationData &rhs) const noexcept
+{
+ return renamedToName == rhs.renamedToName
+ && modifiers == rhs.modifiers
+ && m_snips == rhs.m_snips
+ && m_argument_mods == rhs.m_argument_mods
+ && m_signature == rhs.m_signature
+ && m_originalSignature == rhs.m_originalSignature
+ && m_signaturePattern == rhs.m_signaturePattern
+ && m_overloadNumber == rhs.m_overloadNumber
+ && removed == rhs.removed
+ && inherited == rhs.inherited
+ && m_allowThread == rhs.m_allowThread
+ && m_exceptionHandling == rhs.m_exceptionHandling
+ && snakeCase == rhs.snakeCase;
+}
+
+size_t FunctionModificationData::hash(size_t seed) const noexcept
+{
+ return qHashMulti(seed, renamedToName, modifiers, m_snips, m_argument_mods, m_signature,
+ m_originalSignature, m_signaturePattern, m_overloadNumber, removed,
+ inherited, m_allowThread, m_exceptionHandling, snakeCase);
+}
+
FunctionModification::FunctionModification() : d(new FunctionModificationData)
{
}
FunctionModification &FunctionModification::operator=(FunctionModification &&) noexcept = default;
FunctionModification::~FunctionModification() = default;
+bool FunctionModification::equals(const FunctionModification &rhs) const noexcept
+{
+ return d.constData() == rhs.d.constData() || d->equals(*rhs.d);
+}
+
+size_t FunctionModification::hash(size_t seed) const noexcept
+{
+ return d->hash(seed);
+}
+
void FunctionModification::formatDebug(QDebug &debug) const
{
if (d->m_signature.isEmpty())
d->removed = r;
}
+bool FunctionModification::isInherited() const
+{
+ return d->inherited;
+}
+
+void FunctionModification::setInherited(bool i)
+{
+ if (d->inherited != i)
+ d->inherited = i;
+}
+
const QList<ArgumentModification> &FunctionModification::argument_mods() const
{
return d->m_argument_mods;
if (!d->m_signature.isEmpty())
return functionSignatures.contains(d->m_signature);
- for (const auto &s : functionSignatures) {
- if (d->m_signaturePattern.match(s).hasMatch())
- return true;
- }
- return false;
+ const auto &pattern = d->m_signaturePattern;
+ return std::any_of(functionSignatures.cbegin(), functionSignatures.end(),
+ [&pattern] (const QString &s) { return pattern.match(s).hasMatch(); });
}
bool FunctionModification::setSignature(const QString &s, QString *errorMessage)
#include "documentation_enums.h"
#include "modifications_typedefs.h"
-#include <QtCore/QList>
-#include <QtCore/QSharedDataPointer>
-#include <QtCore/QString>
+#include <QtCore/qlist.h>
+#include <QtCore/qshareddata.h>
+#include <QtCore/qstring.h>
class ArgumentModificationData;
class CodeSnip;
QString varName;
Action action;
+
+ Q_DECLARE_EQUALITY_COMPARABLE(ReferenceCount)
+
+ friend bool comparesEqual(const ReferenceCount &lhs, const ReferenceCount &rhs) noexcept
+ { return lhs.action == rhs.action && lhs.varName == rhs.varName; }
+ friend size_t qHash(const ReferenceCount &r, size_t seed = 0) noexcept
+ { return qHashMulti(seed, r.action, r.varName); }
};
struct ArgumentOwner
Action action = Invalid;
int index = InvalidIndex;
+
+ Q_DECLARE_EQUALITY_COMPARABLE(ArgumentOwner)
+
+ friend bool comparesEqual(const ArgumentOwner &lhs, const ArgumentOwner &rhs) noexcept
+ { return lhs.action == rhs.action && lhs.index == rhs.index; }
+ friend size_t qHash(const ArgumentOwner &a, size_t seed = 0) noexcept
+ { return qHashMulti(seed, a.action, a.index); }
};
class ArgumentModification
void setArray(bool value);
private:
+ bool equals(const ArgumentModification &rhs) const noexcept;
+ size_t hash(size_t seed) const noexcept;
+
+ Q_DECLARE_EQUALITY_COMPARABLE(ArgumentModification)
+
+ friend bool comparesEqual(const ArgumentModification &lhs, const ArgumentModification &rhs) noexcept
+ { return lhs.equals(rhs); }
+ friend size_t qHash(const ArgumentModification &a, size_t seed = 0) noexcept
+ { return a.hash(seed); }
+
QSharedDataPointer<ArgumentModificationData> d;
};
bool isRemoved() const;
void setRemoved(bool r);
+ bool isInherited() const; // inherited from base class
+ void setInherited(bool i);
+
bool isAccessModifier() const
{
return (modifiers() & AccessModifierMask) != 0;
bool isRemoveModifier() const { return isRemoved(); }
-
-
bool isCodeInjection() const
{
return modifiers().testFlag(CodeInjection);
#endif
private:
+ bool equals(const FunctionModification &rhs) const noexcept;
+ size_t hash(size_t seed) const noexcept;
+
+ Q_DECLARE_EQUALITY_COMPARABLE(FunctionModification)
+
+ friend bool comparesEqual(const FunctionModification &lhs, const FunctionModification &rhs) noexcept
+ { return lhs.equals(rhs); }
+ friend size_t qHash(const FunctionModification &f, size_t seed = 0) noexcept
+ { return f.hash(seed); }
+
QSharedDataPointer<FunctionModificationData> d;
};
DocumentationEmphasis emphasis() const { return m_emphasis; }
void setEmphasis(DocumentationEmphasis newEmphasis) { m_emphasis = newEmphasis; }
+ DocumentationTarget target() const { return m_target; }
+ void setTarget(DocumentationTarget newTarget) { m_target = newTarget; }
+
private:
QString m_code;
QString m_xpath;
TypeSystem::DocModificationMode m_mode = TypeSystem::DocModificationXPathReplace;
DocumentationFormat m_format = DocumentationFormat::Native;
DocumentationEmphasis m_emphasis = DocumentationEmphasis::None;
+ DocumentationTarget m_target = DocumentationTarget::Documentation;
};
#endif // MODIFICATIONS_H
#ifndef MODIFICATIONS_TYPEDEFS_H
#define MODIFICATIONS_TYPEDEFS_H
-#include <QtCore/QList>
+#include <QtCore/qlist.h>
#include <memory>
#include "messages.h"
#include "exception.h"
-#include <QtCore/QDir>
-#include <QtCore/QTextStream>
+#include <QtCore/qdir.h>
+#include <QtCore/qtextstream.h>
using namespace Qt::StringLiterals;
bool OptionsParserList::handleBoolOption(const QString &key, OptionSource source)
{
- for (const auto &p : std::as_const(m_parsers)) {
- if (p->handleBoolOption(key, source))
- return true;
- }
- return false;
+ auto pred = [&key, source](const OptionsParserPtr &p) { return p->handleBoolOption(key, source); };
+ return std::any_of(m_parsers.cbegin(), m_parsers.cend(), pred);
}
bool OptionsParserList::handleOption(const QString &key, const QString &value, OptionSource source)
{
- for (const auto &p : std::as_const(m_parsers)) {
- if (p->handleOption(key, value, source))
- return true;
- }
- return false;
+ auto pred = [&key, &value, source](const OptionsParserPtr &p) {
+ return p->handleOption(key, value, source);
+ };
+ return std::any_of(m_parsers.cbegin(), m_parsers.cend(), pred);
}
static void processOption(const QString &o, OptionSource source,
#ifndef OPTIONSPARSER_H
#define OPTIONSPARSER_H
-#include <QtCore/QString>
-#include <QtCore/QStringList>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringlist.h>
#include <memory>
#include <sourcelocation.h>
#include <debughelpers_p.h>
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QRegularExpression>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qregularexpression.h>
#include <algorithm>
// A forward declaration of an enum ("enum class Foo;") is undistinguishable
// from an enum without values ("enum class QCborTag {}"), so, add all
// enums and replace existing ones without values by ones with values.
- const int index = indexOfEnum(item->name());
+ const auto index = indexOfEnum(item->name());
if (index >= 0) {
if (item->hasValues() && !m_enums.at(index)->hasValues())
m_enums[index] = item;
case CodeModel::Constructor:
case CodeModel::CopyConstructor:
case CodeModel::MoveConstructor:
+ case CodeModel::AssignmentOperator:
+ case CodeModel::MoveAssignmentOperator:
case CodeModel::Destructor:
case CodeModel::Signal:
case CodeModel::Slot:
return m_functionType;
auto newType = newTypeOpt.value();
+ // If clang did not pre-detect AssignmentOperator for some operator=(),
+ // it is an assignment from another type which we are not interested in.
+ if (newType == CodeModel::AssignmentOperator) {
+#ifndef CLANG_HAS_ASSIGNMENT_OPERATOR_CHECK
+ // For clang 14 (Yocto), add a manual check.
+ if (m_arguments.size() == 1 && !type().isVoid()
+ && type().qualifiedName() == m_arguments.constFirst()->type().qualifiedName()) {
+ switch (m_arguments.constFirst()->type().referenceType()) {
+ case NoReference:
+ case LValueReference:
+ return CodeModel::AssignmentOperator;
+ case RValueReference:
+ return CodeModel::MoveAssignmentOperator;
+ }
+ }
+#endif // !CLANG_HAS_ASSIGNMENT_OPERATOR_CHECK
+ return CodeModel::OtherAssignmentOperator;
+ }
// It's some sort of dereference operator?!
if (m_arguments.isEmpty()) {
switch (newType) {
#include "enumvalue.h"
#include "typeinfo.h"
-#include <QtCore/QHash>
-#include <QtCore/QSet>
-#include <QtCore/QString>
-#include <QtCore/QStringList>
-#include <QtCore/QList>
-#include <QtCore/QWeakPointer>
+#include <QtCore/qhash.h>
+#include <QtCore/qset.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qsharedpointer.h>
#include <optional>
#include <utility>
Signal,
Slot,
AssignmentOperator,
+ MoveAssignmentOperator,
+ OtherAssignmentOperator, // Assign from some other type
CallOperator,
ConversionOperator,
DereferenceOperator, // Iterator's operator *
};
Q_ENUM(ClassType)
-public:
CodeModel();
virtual ~CodeModel();
Kind_Variable = 10 << FirstKind | Kind_Member
};
-public:
virtual ~_CodeModelItem();
int kind() const;
#ifndef CODEMODEL_FWD_H
#define CODEMODEL_FWD_H
-#include <QtCore/QList>
+#include <QtCore/qlist.h>
#include <memory>
#include "enumvalue.h"
-#include <QtCore/QDebug>
-#include <QtCore/QString>
-#include <QtCore/QTextStream>
+#include <QtCore/qdebug.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qtextstream.h>
using namespace Qt::StringLiterals;
#include <QtCore/qtypes.h>
#include <QtCore/qtclasshelpermacros.h>
-#include <QtCore/QtCompare>
+#include <QtCore/qcompare.h>
QT_FORWARD_DECLARE_CLASS(QDebug)
QT_FORWARD_DECLARE_CLASS(QString)
#include "qtcompat.h"
-#include <QtCore/QDebug>
-#include <QtCore/QStack>
-#include <QtCore/QTextStream>
+#include <QtCore/qdebug.h>
+#include <QtCore/qstack.h>
+#include <QtCore/qtextstream.h>
#include <iostream>
return d->m_qualifiedName;
}
+QString TypeInfo::qualifiedNameString() const
+{
+ return d->m_qualifiedName.join("::"_L1);
+}
+
+QString TypeInfo::qualifiedInstantationName() const
+{
+ QString result = qualifiedNameString();
+ if (const auto instantiationCount = d->m_instantiations.size()) {
+ result += u'<';
+ for (qsizetype i = 0; i < instantiationCount; ++i) {
+ if (i)
+ result += ", "_L1;
+ result += d->m_instantiations.at(i).toString();
+ }
+ if (result.endsWith(u'>'))
+ result += u' ';
+ result += u'>';
+ }
+ return result;
+}
+
void TypeInfo::setQualifiedName(const QStringList &qualified_name)
{
if (d->m_qualifiedName != qualified_name)
// typedef struct xcb_connection_t xcb_connection_t;
if (nextItem.get() ==__item.get()) {
std::cerr << "** WARNING Bailing out recursion of " << __FUNCTION__
- << "() on " << qPrintable(__type.qualifiedName().join(u"::"_s))
+ << "() on " << qPrintable(__type.qualifiedNameString())
<< '\n';
return otherType;
}
if (isVolatile())
tmp += u"volatile "_s;
- tmp += d->m_qualifiedName.join(u"::"_s);
-
- if (const auto instantiationCount = d->m_instantiations.size()) {
- tmp += u'<';
- for (qsizetype i = 0; i < instantiationCount; ++i) {
- if (i)
- tmp += u", "_s;
- tmp += d->m_instantiations.at(i).toString();
- }
- if (tmp.endsWith(u'>'))
- tmp += u' ';
- tmp += u'>';
- }
+ tmp += qualifiedInstantationName();
for (Indirection i : d->m_indirections)
tmp.append(indirectionKeyword(i));
#include "codemodel_enums.h"
#include "codemodel_fwd.h"
-#include <QtCore/QString>
-#include <QtCore/QSharedDataPointer>
-#include <QtCore/QtCompare>
-#include <QtCore/QStringList>
+#include <QtCore/qstring.h>
+#include <QtCore/qshareddata.h>
+#include <QtCore/qcompare.h>
+#include <QtCore/qstringlist.h>
#include <utility>
QStringList qualifiedName() const;
void setQualifiedName(const QStringList &qualified_name);
+ // Returns "std::list"
+ QString qualifiedNameString() const;
+ // Returns qualifiedName() with instantions ("std::list<int>")
+ QString qualifiedInstantationName() const;
+
void addName(const QString &);
bool isVoid() const;
Py_ssize_t idx = 0;
for (; it != upper; ++it, ++idx) {
const auto &cppItem = it.value();
- PyList_SET_ITEM(pyValues, idx, %CONVERTTOPYTHON[%INTYPE_1](cppItem));
+ PyList_SetItem(pyValues, idx, %CONVERTTOPYTHON[%INTYPE_1](cppItem));
}
PyDict_SetItem(%out, pyKey, pyValues);
Py_DECREF(pyKey);
Py_ssize_t idx = 0;
for (; it != range.second; ++it, ++idx) {
const auto &cppItem = it.value();
- PyList_SET_ITEM(pyValues, idx, %CONVERTTOPYTHON[%INTYPE_1](cppItem));
+ PyList_SetItem(pyValues, idx, %CONVERTTOPYTHON[%INTYPE_1](cppItem));
}
PyDict_SetItem(%out, pyKey, pyValues);
Py_DECREF(pyKey);
Py_ssize_t idx = 0;
for (auto it = std::cbegin(%in), end = std::cend(%in); it != end; ++it, ++idx) {
const auto &cppItem = *it;
- PyList_SET_ITEM(%out, idx, %CONVERTTOPYTHON[%INTYPE_0](cppItem));
+ PyList_SetItem(%out, idx, %CONVERTTOPYTHON[%INTYPE_0](cppItem));
}
return %out;)"_s},
const char *targetToNativeType,
const char *targetToNative)
{
- QByteArray result = QByteArrayLiteral("<container-type name=\"")
- + name + QByteArrayLiteral("\" type=\"") + type + R"(">
+ QByteArray result = "<container-type name=\""_ba
+ + name + "\" type=\""_ba + type + R"(">
<include file-name=")" + include + R"(" location="global"/>
<conversion-rule>
<native-to-target>
</native-to-target>
)";
if (targetToNativeType != nullptr) {
- result += QByteArrayLiteral(R"( <target-to-native>
- <add-conversion type=")") + targetToNativeType
- + QByteArrayLiteral(R"(">
- <insert-template name=")") + targetToNative + QByteArrayLiteral(R"("/>
+ result += R"( <target-to-native>
+ <add-conversion type=")"_ba + targetToNativeType
+ + R"(">
+ <insert-template name=")"_ba + targetToNative + R"("/>
</add-conversion>
</target-to-native>
-)");
+)"_ba;
}
-result += QByteArrayLiteral(R"( </conversion-rule>
+result += R"( </conversion-rule>
</container-type>
-)");
+)"_ba;
return result;
}
#ifndef PREDEFINED_TEMPLATES_H
#define PREDEFINED_TEMPLATES_H
-#include <QtCore/QList>
-#include <QtCore/QString>
+#include <QtCore/qlist.h>
+#include <QtCore/qstring.h>
struct PredefinedTemplate
{
#ifndef PRIMITIVETYPEENTRY_H
#define PRIMITIVETYPEENTRY_H
-#include "typesystem.h"
+#include "cpptypeentry.h"
#include "customconversion_typedefs.h"
class PrimitiveTypeEntryPrivate;
/// A PrimitiveTypeEntry is user-defined type with conversion rules, a C++
/// primitive type for which a PrimitiveTypeConverter exists in libshiboken
/// or a typedef to a C++ primitive type as determined by AbstractMetaBuilder.
-class PrimitiveTypeEntry : public TypeEntry
+class PrimitiveTypeEntry : public CppTypeEntry
{
public:
explicit PrimitiveTypeEntry(const QString &entryName, const QVersionNumber &vr,
const TypeEntryCPtr &parent);
- QString defaultConstructor() const;
- void setDefaultConstructor(const QString& defaultConstructor);
- bool hasDefaultConstructor() const;
-
/**
* The PrimitiveTypeEntry pointed by this type entry if it
* represents a typedef).
* Defines type referenced by this entry.
* \param referencedTypeEntry type referenced by this entry
*/
- void setReferencedTypeEntry(PrimitiveTypeEntryPtr referencedTypeEntry);
+ void setReferencedTypeEntry(const PrimitiveTypeEntryPtr &referencedTypeEntry);
/// Returns whether this entry references another entry.
bool referencesType() const;
#include "qtcompat.h"
-#include <QtCore/QHash>
+#include <QtCore/qhash.h>
#ifndef QT_NO_DEBUG_STREAM
-# include <QtCore/QDebug>
+# include <QtCore/qdebug.h>
#endif
#include <algorithm>
return result;
}
- const auto firstToken = qsizetype(it - propertyTokens.cbegin());
+ const auto firstToken = it - propertyTokens.cbegin();
if (firstToken < 2) {
*errorMessage = u"Insufficient number of tokens in property specification"_s;
return result;
#include "abstractmetalang_typedefs.h"
#include "typesystem_typedefs.h"
-#include <QtCore/QStringList>
-#include <QtCore/QSharedDataPointer>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qshareddata.h>
#include <optional>
#include "pymethoddefentry.h"
#include "textstream.h"
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
TextStream &operator<<(TextStream &str, const castToPyCFunction &c)
{
#ifndef PYMETHODDEFENTRY_H
#define PYMETHODDEFENTRY_H
-#include <QtCore/QByteArrayList>
-#include <QtCore/QString>
+#include <QtCore/qbytearraylist.h>
+#include <QtCore/qstring.h>
QT_FORWARD_DECLARE_CLASS(QDebug)
#endif
protected:
- explicit PythonTypeEntry(TypeEntryPrivate *d);
+ explicit PythonTypeEntry(CustomTypeEntryPrivate *d);
};
#endif // PYTHONTYPEENTRY_H
// QTBUG-98434, provide literals of Qt 6.4 for compatibility.
-# include <QtCore/QString>
+# include <QtCore/qstring.h>
# define QLatin1StringView QLatin1String
#include "qtcompat.h"
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QHash>
-#include <QtCore/QUrl>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qurl.h>
#include <algorithm>
#include <iterator>
doc.insert(firstPara + 6, note);
}
}
+ // Fully qualify enums: "<term>QFrame::NoFrame" -> "<term>QFrame::FrameShape::NoFrame"
+ const QString classQualifier = "<term>"_L1 + classDocumentation.name + "::"_L1;
+ doc.replace(classQualifier, classQualifier + meta_enum.name() + "::"_L1);
+ doc.replace("::None</term>"_L1, "::None\\_</term>"_L1);
Documentation enumDoc(doc, {}, sourceFileName);
meta_enum.setDocumentation(enumDoc);
return true;
#include "reporthandler.h"
#include "typedatabase.h"
+#include "messages.h"
-#include "qtcompat.h"
+#include <QtCore/qelapsedtimer.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qoperatingsystemversion.h>
+#include <QtCore/qset.h>
-#include <QtCore/QElapsedTimer>
-#include <QtCore/QSet>
#include <cstring>
#include <cstdarg>
#include <cstdio>
using namespace Qt::StringLiterals;
-#if defined(_WINDOWS) || defined(NOCOLOR)
- #define COLOR_END ""
- #define COLOR_WHITE ""
- #define COLOR_YELLOW ""
- #define COLOR_GREEN ""
-#else
- #define COLOR_END "\033[0m"
- #define COLOR_WHITE "\033[1;37m"
- #define COLOR_YELLOW "\033[1;33m"
- #define COLOR_GREEN "\033[0;32m"
+static const auto COLOR_END = "\033[0m"_ba;
+static const auto COLOR_YELLOW = "\033[1;33m"_ba;
+static const auto COLOR_GREEN = "\033[0;32m"_ba;
+
+static constexpr bool useTerminalColors()
+{
+ return QOperatingSystemVersion::currentType() != QOperatingSystemVersion::Windows
+#ifdef NOCOLOR
+ && false
#endif
+ ;
+}
static bool m_silent = false;
static int m_warningCount = 0;
std::fputs(m_progressMessage.constData(), stdout);
if (m_progressMessage.size() < 60)
indentStdout(60 - m_progressMessage.size());
- const char *endMessage = m_step_warning == 0
- ? "[" COLOR_GREEN "OK" COLOR_END "]\n"
- : "[" COLOR_YELLOW "WARNING" COLOR_END "]\n";
- std::fputs(endMessage, stdout);
+ static const QByteArray ok = '['
+ + (useTerminalColors() ? COLOR_GREEN + "OK"_ba + COLOR_END : "OK"_ba)
+ + "]\n"_ba;
+ static const QByteArray warning = '['
+ + (useTerminalColors() ? COLOR_YELLOW + "WARNING"_ba + COLOR_END : "WARNING"_ba)
+ + "]\n"_ba;
+ std::fputs(m_step_warning == 0 ? ok.constData() : warning.constData(), stdout);
std::fflush(stdout);
m_progressMessage.clear();
m_step_warning = 0;
result += " (" + QByteArray::number(m_suppressedCount) + " known issues)";
return result;
}
+
+static QStringList generalMessages;
+
+void ReportHandler::addGeneralMessage(const QString &message)
+{
+ generalMessages.append(message);
+}
+
+void ReportHandler::writeGeneralLogFile(const QString &directory)
+{
+ if (generalMessages.isEmpty())
+ return;
+ QFile file(directory + "/mjb_shiboken.log"_L1);
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
+ qWarning(lcShiboken, "%s", qPrintable(msgCannotOpenForWriting(file)));
+ return;
+ }
+ for (const auto &m : std::as_const(generalMessages)) {
+ file.write(m.toUtf8());
+ file.putChar('\n');
+ }
+}
#ifndef REPORTHANDLER_H
#define REPORTHANDLER_H
-#include <QtCore/QLoggingCategory>
-#include <QtCore/QString>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qstring.h>
Q_DECLARE_LOGGING_CATEGORY(lcShiboken)
Q_DECLARE_LOGGING_CATEGORY(lcShibokenDoc)
static QByteArray doneMessage();
+ static void addGeneralMessage(const QString &message);
+ static void writeGeneralLogFile(const QString &directory);
+
private:
static void messageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg);
};
TypeEntry *clone() const override;
+ bool matchesInstantiation(const TypeEntryCPtr &e) const;
+
const Instantiations &instantiations() const;
void setInstantiations(const Instantiations &i);
- bool matchesInstantiation(const TypeEntryCPtr &e) const;
+
+ void setExcludedInstantiations(const TypeEntryCList &ex);
+ const TypeEntryCList &excludedInstantiations() const;
QString getTargetName(const AbstractMetaType &metaType) const;
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "sourcelocation.h"
-#include <QtCore/QDir>
-#include <QtCore/QDebug>
+#include <QtCore/qdir.h>
+#include <QtCore/qdebug.h>
SourceLocation::SourceLocation() = default;
#ifndef SOURCE_LOCATION_H
#define SOURCE_LOCATION_H
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
QT_FORWARD_DECLARE_CLASS(QDebug)
QT_FORWARD_DECLARE_CLASS(QTextStream)
#include <qtcompat.h>
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
using namespace Qt::StringLiterals;
QCOMPARE(ctors.constFirst()->minimalSignature(), u"A()");
}
+void TestAbstractMetaClass::testValueConstructors_data()
+{
+ QTest::addColumn<QByteArray>("cppCode");
+ QTest::addColumn<bool>("expectedDefaultConstructible");
+ QTest::addColumn<bool>("expectedCopyConstructible");
+
+ QByteArray cppCode = R"(class A {};)";
+
+ QTest::newRow("implicit copy/default") << cppCode << true << true;
+
+ cppCode = R"(class A {
+private:
+ A();
+ A(const A &);
+public:
+};)";
+
+ QTest::newRow("old style - private copy/default") << cppCode << false << false;
+
+ cppCode = R"(class A {
+public:
+ A() = delete;
+ A(const A &) = delete;
+};)";
+
+ QTest::newRow("deleted copy/default") << cppCode << false << false;
+
+ cppCode = R"(class A {
+public:
+ A() = default;
+ A &operator=(const A &) = delete;
+};)";
+
+ QTest::newRow("default/deleted assignment") << cppCode << true << false;
+
+ cppCode = R"(class A {
+public:
+ A() = default;
+ A &operator=(A &&) = delete;
+};)";
+
+ QTest::newRow("default/deleted move assignment") << cppCode << true << false;
+
+ // An assignment from another type should not impact copy copy constructibility
+ cppCode = R"(class A {
+public:
+ A() = default;
+ A &operator=(int v);
+};)";
+
+ QTest::newRow("assignment from other type") << cppCode << true << true;
+
+ // Another constructor present, move deleted -> non default copy/default constructible
+ cppCode = R"(class A {
+public:
+ A(int x);
+ A(A &&) = delete;
+};)";
+
+ QTest::newRow("param-constructor, deleted move") << cppCode << false << false;
+}
+
+void TestAbstractMetaClass::testValueConstructors()
+{
+ QFETCH(QByteArray, cppCode);
+ QFETCH(bool, expectedDefaultConstructible);
+ QFETCH(bool, expectedCopyConstructible);
+
+ const char xmlCode[] = R"(<typesystem package='Foo'>
+ <value-type name='A'/>
+</typesystem>
+)";
+
+ QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode.constData(), xmlCode));
+ QVERIFY(builder);
+ AbstractMetaClassList classes = builder->classes();
+ QCOMPARE(classes.size(), 1);
+ const auto classA = AbstractMetaClass::findClass(classes, "A");
+ QVERIFY(classA);
+ QCOMPARE(classA->isDefaultConstructible(), expectedDefaultConstructible);
+ QCOMPARE(classA->isCopyConstructible(), expectedCopyConstructible);
+}
+
void TestAbstractMetaClass::testIsPolymorphic()
{
const char cppCode[] = "\
#ifndef TESTABSTRACTMETACLASS_H
#define TESTABSTRACTMETACLASS_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class AbstractMetaBuilder;
void testClassInheritedDefaultConstructors();
void testAbstractClassDefaultConstructors();
void testObjectTypesMustNotHaveCopyConstructors();
+ void testValueConstructors_data();
+ void testValueConstructors();
void testIsPolymorphic();
void testClassTypedefedBaseClass();
void testFreeOperators_data();
#include <qtcompat.h>
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
using namespace Qt::StringLiterals;
#ifndef TESTABSTRACTMETATYPE_H
#define TESTABSTRACTMETATYPE_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestAbstractMetaType : public QObject
{
#include <qtcompat.h>
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
using namespace Qt::StringLiterals;
#ifndef TESTADDFUNCTION_H
#define TESTADDFUNCTION_H
-#include <QtCore/QObject>
+
+#include <QtCore/qobject.h>
class TestAddFunction : public QObject
{
#include <qtcompat.h>
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
using namespace Qt::StringLiterals;
#ifndef TESTARRAYARGUMENT_H
#define TESTARRAYARGUMENT_H
-#include <QtCore/QObject>
+
+#include <QtCore/qobject.h>
class TestArrayArgument : public QObject
{
#include <qtcompat.h>
-#include <QtCore/QDir>
-#include <QtCore/QFileInfo>
-#include <QtTest/QTest>
+#include <QtCore/qdir.h>
+#include <QtCore/qfileinfo.h>
+
+#include <QtTest/qtest.h>
using namespace Qt::StringLiterals;
#ifndef TESTCODEINJECTIONS_H
#define TESTCODEINJECTIONS_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class AbstractMetaBuilder;
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "testcontainer.h"
-#include <QtTest/QTest>
#include "testutil.h"
+
#include <abstractmetalang.h>
#include <abstractmetatype.h>
#include <complextypeentry.h>
#include <containertypeentry.h>
+#include <QtTest/qtest.h>
+
void TestContainer::testContainerType()
{
const char cppCode[] = "\
#ifndef TESTCONTAINER_H
#define TESTCONTAINER_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestContainer : public QObject
{
#include <qtcompat.h>
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
using namespace Qt::StringLiterals;
#ifndef TESTCONVERSIONOPERATOR_H
#define TESTCONVERSIONOPERATOR_H
-#include <QtCore/QObject>
+
+#include <QtCore/qobject.h>
class TestConversionOperator : public QObject
{
#include <qtcompat.h>
-#include <QtCore/QFile>
-#include <QtCore/QTemporaryFile>
-#include <QtTest/QTest>
+#include <QtCore/qfile.h>
+#include <QtCore/qtemporaryfile.h>
+
+#include <QtTest/qtest.h>
using namespace Qt::StringLiterals;
#ifndef TESTCONVERSIONRULE_H
#define TESTCONVERSIONRULE_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestConversionRuleTag : public QObject
{
#include "testctorinformation.h"
#include "abstractmetabuilder.h"
-#include <QtTest/QTest>
#include "testutil.h"
+
#include <abstractmetalang.h>
#include <typesystem.h>
+#include <QtTest/qtest.h>
+
void TestCtorInformation::testCtorIsPrivate()
{
const char cppCode[] = "class Control { public: Control() {} };\n\
#ifndef TESTCTORINFORMATION_H
#define TESTCTORINFORMATION_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class AbstractMetaBuilder;
#include <qtcompat.h>
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
using namespace Qt::StringLiterals;
#ifndef TESTDROPTYPEENTRIES_H
#define TESTDROPTYPEENTRIES_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestDropTypeEntries : public QObject
{
#include "testdtorinformation.h"
#include "abstractmetabuilder.h"
-#include <QtTest/QTest>
#include "testutil.h"
#include <abstractmetalang.h>
#include <typesystem.h>
+#include <QtTest/qtest.h>
+
void TestDtorInformation::testDtorIsPrivate()
{
const char cppCode[] = R"(class Control {
#ifndef TESTDTORINFORMATION_H
#define TESTDTORINFORMATION_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class AbstractMetaBuilder;
#include <qtcompat.h>
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
using namespace Qt::StringLiterals;
#ifndef TESTENUM_H
#define TESTENUM_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestEnum : public QObject
{
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "testextrainclude.h"
-#include <QtTest/QTest>
#include "testutil.h"
+
#include <abstractmetalang.h>
#include <complextypeentry.h>
#include <typesystemtypeentry.h>
+#include <QtTest/qtest.h>
+
void TestExtraInclude::testClassExtraInclude()
{
const char cppCode[] = "struct A {};\n";
#ifndef TESTEXTRAINCLUDE_H
#define TESTEXTRAINCLUDE_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestExtraInclude : public QObject
{
#include <qtcompat.h>
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
using namespace Qt::StringLiterals;
#ifndef TESTFUNCTIONTAG_H
#define TESTFUNCTIONTAG_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestFunctionTag : public QObject
{
#include "testimplicitconversions.h"
#include "testutil.h"
+
#include <abstractmetaargument.h>
#include <abstractmetafunction.h>
#include <abstractmetalang.h>
#include <abstractmetatype.h>
#include <complextypeentry.h>
-#include <QtTest/QTest>
+
+#include <QtTest/qtest.h>
void TestImplicitConversions::testWithPrivateCtors()
{
#ifndef TESTIMPLICITCONVERSIONS_H
#define TESTIMPLICITCONVERSIONS_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class AbstractMetaBuilder;
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "testinserttemplate.h"
-#include <QtTest/QTest>
#include "testutil.h"
+
#include <abstractmetalang.h>
#include <codesnip.h>
#include <modifications.h>
#include <complextypeentry.h>
#include <typesystemtypeentry.h>
+#include <QtTest/qtest.h>
+
void TestInsertTemplate::testInsertTemplateOnClassInjectCode()
{
const char cppCode[] = "struct A{};\n";
#ifndef TESTINSERTTEMPLATE_H
#define TESTINSERTTEMPLATE_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestInsertTemplate : public QObject
{
#include <qtcompat.h>
-#include <QtCore/QCoreApplication>
-#include <QtCore/QTemporaryDir>
-#include <QtTest/QTest>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qtemporarydir.h>
+#include <QtTest/qtest.h>
using namespace Qt::StringLiterals;
#ifndef TESTMODIFYDOCUMENTATION_H
#define TESTMODIFYDOCUMENTATION_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestModifyDocumentation : public QObject
{
#include <qtcompat.h>
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
using namespace Qt::StringLiterals;
#ifndef TESTABSTRACTMETACLASS_H
#define TESTABSTRACTMETACLASS_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestModifyFunction : public QObject
{
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "testmultipleinheritance.h"
-#include <QtTest/QTest>
#include "testutil.h"
+
#include <abstractmetafunction.h>
#include <abstractmetalang.h>
#include <typesystem.h>
+#include <QtTest/qtest.h>
+
void TestMultipleInheritance::testVirtualClass()
{
const char cppCode[] = "\
#ifndef TESTMULTIPLEINHERITANCE_H
#define TESTMULTIPLEINHERITANCE_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class AbstractMetaBuilder;
#include <qtcompat.h>
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
using namespace Qt::StringLiterals;
#ifndef TESTNAMESPACE_H
#define TESTNAMESPACE_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
// The class is named 'NamespaceTest' to avoid clashes with Qt COIN using
// '-qtnamespace TestNamespace'.
#include <qtcompat.h>
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
using namespace Qt::StringLiterals;
#ifndef TESTNESTEDTYPES_H
#define TESTNESTEDTYPES_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestNestedTypes : public QObject
{
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "testnumericaltypedef.h"
-#include <QtTest/QTest>
#include "testutil.h"
+
#include <abstractmetaargument.h>
#include <abstractmetafunction.h>
#include <abstractmetalang.h>
#include <abstractmetatype.h>
#include <typesystem.h>
+#include <QtTest/qtest.h>
+
void TestNumericalTypedef::testNumericalTypedef()
{
const char cppCode[] = "\
#ifndef TESTNUMERICALTYPEDEF_H
#define TESTNUMERICALTYPEDEF_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestNumericalTypedef : public QObject
{
#include <qtcompat.h>
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
using namespace Qt::StringLiterals;
#ifndef TESTPRIMITIVETYPETAG_H
#define TESTPRIMITIVETYPETAG_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestPrimitiveTypeTag : public QObject
{
#include <qtcompat.h>
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
using namespace Qt::StringLiterals;
#ifndef TESTREFCOUNTTAG_H
#define TESTREFCOUNTTAG_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestRefCountTag : public QObject
{
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "testreferencetopointer.h"
-#include <QtTest/QTest>
#include "testutil.h"
+
#include <abstractmetaargument.h>
#include <abstractmetafunction.h>
#include <abstractmetalang.h>
#include <abstractmetatype.h>
#include <typesystem.h>
+#include <QtTest/qtest.h>
+
void TestReferenceToPointer::testReferenceToPointerArgument()
{
const char cppCode[] = "\
#ifndef TESTREFERENCETOPOINTER_H
#define TESTREFERENCETOPOINTER_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestReferenceToPointer : public QObject
{
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "testremovefield.h"
-#include <QtTest/QTest>
#include "testutil.h"
+
#include <abstractmetaargument.h>
#include <abstractmetafield.h>
#include <abstractmetafunction.h>
#include <abstractmetalang.h>
#include <typesystem.h>
+#include <QtTest/qtest.h>
+
using namespace Qt::StringLiterals;
void TestRemoveField::testRemoveField()
#ifndef TESTREMOVEFIELD_H
#define TESTREMOVEFIELD_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestRemoveField : public QObject
{
#include "testremoveimplconv.h"
#include "testutil.h"
-#include <QtTest/QTest>
+
#include <abstractmetaargument.h>
#include <abstractmetafunction.h>
#include <abstractmetalang.h>
#include <abstractmetatype.h>
#include <complextypeentry.h>
+#include <QtTest/qtest.h>
+
// When a constructor able to trigger implicity conversions is removed
// it should not appear in the implicity conversion list.
void TestRemoveImplConv::testRemoveImplConv()
#ifndef TESTREMOVEIMPLCONV_H
#define TESTREMOVEIMPLCONV_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestRemoveImplConv : public QObject
{
#include <qtcompat.h>
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
using namespace Qt::StringLiterals;
#ifndef TESTREMOVEOPERATORMETHOD_H
#define TESTREMOVEOPERATORMETHOD_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestRemoveOperatorMethod : public QObject
{
#include <qtcompat.h>
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
using namespace Qt::StringLiterals;
#ifndef TESTRESOLVETYPE_H
#define TESTRESOLVETYPE_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestResolveType : public QObject
{
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "testreverseoperators.h"
-#include <QtTest/QTest>
#include "testutil.h"
+
#include <abstractmetaargument.h>
#include <abstractmetafunction.h>
#include <abstractmetalang.h>
#include <typesystem.h>
#include <clangparser/compilersupport.h>
+#include <QtTest/qtest.h>
+
#include <algorithm>
void TestReverseOperators::testReverseSum()
#ifndef TESTREVERSEOPERATORS_H
#define TESTREVERSEOPERATORS_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestReverseOperators : public QObject
{
#include "testtemplates.h"
#include "testutil.h"
+
#include <abstractmetaargument.h>
#include <abstractmetafield.h>
#include <abstractmetafunction.h>
#include <qtcompat.h>
-#include <QtCore/QTemporaryFile>
-#include <QtCore/QTextStream>
-#include <QtTest/QTest>
+#include <QtCore/qtemporaryfile.h>
+#include <QtCore/qtextstream.h>
+#include <QtTest/qtest.h>
using namespace Qt::StringLiterals;
#ifndef TESTTEMPLATES_H
#define TESTTEMPLATES_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestTemplates : public QObject
{
#include "testtoposort.h"
#include "graph.h"
-#include <QtTest/QTest>
-#include <QtCore/QDebug>
+#include <QtTest/qtest.h>
+#include <QtCore/qdebug.h>
using IntGraph = Graph<int>;
#ifndef TESTTOPOSORT_H
#define TESTTOPOSORT_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestTopoSort : public QObject
{
#include <qtcompat.h>
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
using namespace Qt::StringLiterals;
#ifndef TESTTYPEREVISION_H
#define TESTTYPEREVISION_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestTypeRevision : public QObject
{
#ifndef TESTUTIL_H
#define TESTUTIL_H
-#include <QtCore/QBuffer>
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QTemporaryFile>
-#include "abstractmetabuilder.h"
-#include "reporthandler.h"
-#include "typedatabase.h"
+
+#include <abstractmetabuilder.h>
+#include <reporthandler.h>
+#include <typedatabase.h>
+
+#include <QtCore/qbuffer.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qtemporaryfile.h>
#include <exception>
#include <memory>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "testvaluetypedefaultctortag.h"
-#include <QtTest/QTest>
#include "testutil.h"
+
#include <abstractmetalang.h>
#include <complextypeentry.h>
+#include <QtTest/qtest.h>
+
void TestValueTypeDefaultCtorTag::testValueTypeDefaultCtorTagArgument()
{
const char cppCode[] = "\n\
#ifndef TESTVALUETYPEDEFAULTCTORTAG_H
#define TESTVALUETYPEDEFAULTCTORTAG_H
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class TestValueTypeDefaultCtorTag : public QObject
{
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "testvoidarg.h"
-#include <QtTest/QTest>
#include "testutil.h"
+
#include <abstractmetaargument.h>
#include <abstractmetafunction.h>
#include <abstractmetalang.h>
#include <typesystem.h>
+#include <QtTest/qtest.h>
+
void TestVoidArg::testVoidParsedFunction()
{
const char cppCode[] = "struct A { void a(void); };";
#ifndef TESTVOIDARG_H
#define TESTVOIDARG_H
-#include <QtCore/QObject>
+
+#include <QtCore/qobject.h>
class TestVoidArg : public QObject
{
#ifndef TEXTSTREAM_H
#define TEXTSTREAM_H
-#include <QtCore/QTextStream>
-#include <QtCore/QString>
+#include <QtCore/qtextstream.h>
+#include <QtCore/qstring.h>
/// A text stream based on QTextStream with built-in indent.
class TextStream
TextStream &operator<<(qsizetype t) { putSizeType(t); return *this; }
#endif
- inline TextStream &operator<<(const QTextStreamManipulator &m) { m_str << m; return *this; }
- inline TextStream &operator<<(ManipulatorFunc f) { f(*this); return *this; }
+ TextStream &operator<<(const QTextStreamManipulator &m) { m_str << m; return *this; }
+ TextStream &operator<<(ManipulatorFunc f) { f(*this); return *this; }
void putRepetitiveChars(char c, int count);
#include "qtcompat.h"
-#include <QtCore/QBuffer>
-#include <QtCore/QFile>
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QList>
-#include <QtCore/QRegularExpression>
-#include <QtCore/QVersionNumber>
-#include <QtCore/QXmlStreamReader>
+#include <QtCore/qbuffer.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qversionnumber.h>
+#include <QtCore/qxmlstream.h>
#include "reporthandler.h"
#include <algorithm>
TypeEntryPtr resolveTypeDefEntry(const TypedefEntryPtr &typedefEntry, QString *errorMessage);
template <class String>
bool isSuppressedWarningHelper(const String &s) const;
- bool resolveSmartPointerInstantiations(const TypeDatabaseParserContextPtr &context);
+ bool resolveSmartPointerInstantiations(const TypeDatabaseParserContextPtr &context) const;
+ bool resolveSmartPointerInstantiations(const SmartPointerTypeEntryPtr &smartPointerEntry,
+ const QString &s) const;
+ bool resolveSmartPointerExclusions(const SmartPointerTypeEntryPtr &smartPointerEntry,
+ const QString &s) const;
void formatDebug(QDebug &d) const;
void formatBuiltinTypes(QDebug &d) const;
AddedFunctionList m_globalUserFunctions;
FunctionModificationList m_functionMods;
+ OverloadRemovalRules m_overloadRemovalRules;
QStringList m_requiredTargetImports;
void TypeDatabase::addTemplate(const QString &name, const QString &code)
{
- auto te = std::make_shared<TemplateEntry>(name);
+ auto te = std::make_shared<TemplateEntry>();
+ te->setName(name);
te->addCode(code);
addTemplate(te);
}
return lst;
}
+const OverloadRemovalRules &TypeDatabase::overloadRemovalRules() const
+{
+ return d->m_overloadRemovalRules;
+}
+
+void TypeDatabase::addOverloadRemovalRule(const OverloadRemovalRule &r)
+{
+ d->m_overloadRemovalRules.append(r);
+}
+
bool TypeDatabase::addSuppressedWarning(const QString &warning, bool generate,
QString *errorMessage)
{
bool TypeDatabase::parseFile(const TypeDatabaseParserContextPtr &context,
QIODevice *device, bool generate)
{
- return d->parseFile(context, device, generate);
+ return TypeDatabasePrivate::parseFile(context, device, generate);
}
bool TypeDatabasePrivate::parseFile(const TypeDatabaseParserContextPtr &context,
QIODevice *device, bool generate)
{
ConditionalStreamReader reader(device);
- reader.setConditions(context->db->typesystemKeywords());
+ const auto typesystemKeywords = context->db->typesystemKeywords();
+ if (generate) {
+ ReportHandler::addGeneralMessage("Type System keywords: "_L1
+ + typesystemKeywords.join(", "_L1) + u'\n');
+ }
+ reader.setConditions(typesystemKeywords);
TypeSystemParser handler(context, generate);
const bool result = handler.parse(reader);
if (!result) {
lastPos = i + 1;
}
break;
+ default:
+ break;
}
}
if (lastPos < size)
return result;
}
-bool TypeDatabasePrivate::resolveSmartPointerInstantiations(const TypeDatabaseParserContextPtr &context)
+bool TypeDatabasePrivate::resolveSmartPointerInstantiations(
+ const SmartPointerTypeEntryPtr &smartPointerEntry, const QString &s) const
+{
+ const auto instantiationNames = splitTypeList(s);
+ SmartPointerTypeEntry::Instantiations instantiations;
+ instantiations.reserve(instantiationNames.size());
+ for (const auto &instantiation : instantiationNames) {
+ QString name;
+ QString type = instantiation;
+ const auto equalsPos = instantiation.indexOf(u'=');
+ if (equalsPos != -1) {
+ type.truncate(equalsPos);
+ name = instantiation.mid(equalsPos + 1);
+ }
+
+ const auto typeEntries = findCppTypes(type);
+ if (typeEntries.isEmpty()) {
+ const QString m = msgCannotFindTypeEntryForSmartPointer(type,
+ smartPointerEntry->name());
+ qCWarning(lcShiboken, "%s", qPrintable(m));
+ return false;
+ }
+ if (typeEntries.size() > 1) {
+ const QString m = msgAmbiguousTypesFound(type, typeEntries);
+ qCWarning(lcShiboken, "%s", qPrintable(m));
+ return false;
+ }
+ instantiations.append({name, typeEntries.constFirst()});
+ }
+ smartPointerEntry->setInstantiations(instantiations);
+ return true;
+}
+
+bool TypeDatabasePrivate::resolveSmartPointerExclusions(
+ const SmartPointerTypeEntryPtr &smartPointerEntry, const QString &s) const
+{
+ const auto excludedNames = splitTypeList(s);
+ TypeEntryCList excluded;
+
+ excluded.reserve(excludedNames.size());
+ for (const auto &excludedName : excludedNames) {
+ const auto typeEntries = findCppTypes(excludedName);
+ if (typeEntries.isEmpty()) {
+ const QString m = msgCannotFindTypeEntryForSmartPointer(excludedName,
+ smartPointerEntry->name());
+ qCWarning(lcShiboken, "%s", qPrintable(m));
+ return false;
+ }
+ if (typeEntries.size() > 1) {
+ const QString m = msgAmbiguousTypesFound(excludedName, typeEntries);
+ qCWarning(lcShiboken, "%s", qPrintable(m));
+ return false;
+ }
+ excluded.append(typeEntries.constFirst());
+ }
+ smartPointerEntry->setExcludedInstantiations(excluded);
+ return true;
+}
+
+bool TypeDatabasePrivate::resolveSmartPointerInstantiations(const TypeDatabaseParserContextPtr &context) const
{
const auto &instantiations = context->smartPointerInstantiations;
for (auto it = instantiations.cbegin(), end = instantiations.cend(); it != end; ++it) {
- const auto &smartPointerEntry = it.key();
- const auto instantiationNames = splitTypeList(it.value());
- SmartPointerTypeEntry::Instantiations instantiations;
- instantiations.reserve(instantiationNames.size());
- for (const auto &instantiation : instantiationNames) {
- QString name;
- QString type = instantiation;
- const auto equalsPos = instantiation.indexOf(u'=');
- if (equalsPos != -1) {
- type.truncate(equalsPos);
- name = instantiation.mid(equalsPos + 1);
- }
-
- const auto typeEntries = findCppTypes(type);
- if (typeEntries.isEmpty()) {
- const QString m = msgCannotFindTypeEntryForSmartPointer(type,
- smartPointerEntry->name());
- qCWarning(lcShiboken, "%s", qPrintable(m));
- return false;
- }
- if (typeEntries.size() > 1) {
- const QString m = msgAmbiguousTypesFound(type, typeEntries);
- qCWarning(lcShiboken, "%s", qPrintable(m));
- return false;
- }
- instantiations.append({name, typeEntries.constFirst()});
+ const auto &entry = it.value();
+ if ((!entry.instantiations.isEmpty()
+ && !resolveSmartPointerInstantiations(it.key(), entry.instantiations))
+ || (!entry.excludedInstantiations.isEmpty()
+ && !resolveSmartPointerExclusions(it.key(), entry.excludedInstantiations))) {
+ return false;
}
- smartPointerEntry->setInstantiations(instantiations);
}
return true;
}
if (versionNumber.isNull())
return false;
ApiVersions &versions = *apiVersions();
- for (qsizetype i = 0, size = versions.size(); i < size; ++i) {
- if (versions.at(i).first.pattern() == packagePattern) {
- versions[i].second = versionNumber;
+ for (auto &version : versions) {
+ if (version.first.pattern() == packagePattern) {
+ version.second = versionNumber;
return true;
}
}
const ApiVersions &versions = *apiVersions();
if (versions.isEmpty()) // Nothing specified: use latest.
return true;
- for (qsizetype i = 0, size = versions.size(); i < size; ++i) {
- if (versions.at(i).first.match(package).hasMatch())
- return versions.at(i).second >= vr.since
- && versions.at(i).second <= vr.until;
+ for (const auto &version : versions) {
+ if (version.first.match(package).hasMatch())
+ return version.second >= vr.since
+ && version.second <= vr.until;
}
return false;
}
#include "modifications_typedefs.h"
#include "typedatabase_typedefs.h"
-#include <QtCore/QRegularExpression>
-#include <QtCore/QStringList>
-#include <QtCore/QVersionNumber>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qversionnumber.h>
#include <memory>
QDebug operator<<(QDebug d, const TypeRejection &r);
#endif
+// Rule for removing overloads that differ in one type
+struct OverloadRemovalRule
+{
+ QString type;
+ QStringList redundantTypes;
+};
+
+using OverloadRemovalRules = QList<OverloadRemovalRule>;
+
class TypeDatabase
{
TypeDatabase();
FunctionModificationList
globalFunctionModifications(const QStringList &signatures) const;
+ const OverloadRemovalRules &overloadRemovalRules() const;
+ void addOverloadRemovalRule(const OverloadRemovalRule &r);
+
bool addSuppressedWarning(const QString &warning, bool generate, QString *errorMessage);
bool isSuppressedWarning(QStringView s) const;
// Top level QIODevice parsing for tests.
bool parseFile(QIODevice *device, bool generate = true);
- bool parseFile(const std::shared_ptr<TypeDatabaseParserContext> &context,
- QIODevice *device, bool generate = true);
+ static bool parseFile(const std::shared_ptr<TypeDatabaseParserContext> &context,
+ QIODevice *device, bool generate = true);
static bool setApiVersion(const QString &package, const QString &version);
static void clearApiVersions();
#include "typesystem_typedefs.h"
#include "containertypeentry.h"
-#include <QtCore/QHash>
-#include <QtCore/QString>
+#include <QtCore/qhash.h>
+#include <QtCore/qstring.h>
class TypeDatabase;
struct TypeDatabaseParserContext
{
- using SmartPointerInstantiations = QHash<SmartPointerTypeEntryPtr, QString>;
+ struct SmartPointerEntry
+ {
+ QString instantiations;
+ QString excludedInstantiations;
+ };
+
+ using SmartPointerInstantiations = QHash<SmartPointerTypeEntryPtr, SmartPointerEntry>;
using OpaqueContainerHash = QHash<QString, OpaqueContainers>;
TypeDatabase *db;
#include "typesystem_typedefs.h"
-#include <QtCore/QMultiMap>
-#include <QtCore/QString>
-#include <QtCore/QList>
+#include <QtCore/qmap.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qlist.h>
using TemplateEntryMap =QMap<QString, TemplateEntryPtr>;
void setSource(const ComplexTypeEntryCPtr &source);
ComplexTypeEntryPtr target() const;
- void setTarget(ComplexTypeEntryPtr target);
+ void setTarget(const ComplexTypeEntryPtr &target);
#ifndef QT_NO_DEBUG_STREAM
void formatDebug(QDebug &d) const override;
#include "typeparser.h"
#include <typeinfo.h>
-#include <QtCore/QDebug>
-#include <QtCore/QStack>
-#include <QtCore/QTextStream>
+#include <QtCore/qdebug.h>
+#include <QtCore/qstack.h>
+#include <QtCore/qtextstream.h>
using namespace Qt::StringLiterals;
tok = VolatileToken;
}
break;
+ default:
+ break;
}
}
#ifndef TYPEPARSER_H
#define TYPEPARSER_H
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
class TypeInfo;
#include "typesystem.h"
#include "arraytypeentry.h"
#include "codesnip.h"
+#include "cpptypeentry.h"
#include "complextypeentry.h"
#include "configurabletypeentry.h"
#include "constantvaluetypeentry.h"
#include "qtcompat.h"
-#include <QtCore/QDebug>
-#include <QtCore/QRegularExpression>
-#include <QtCore/QSet>
-#include <QtCore/QVarLengthArray>
+#include <QtCore/qdebug.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qset.h>
+#include <QtCore/qvarlengtharray.h>
using namespace Qt::StringLiterals;
QVersionNumber m_version;
SourceLocation m_sourceLocation; // XML file
TypeEntry::CodeGeneration m_codeGeneration = TypeEntry::GenerateCode;
- TypeEntryPtr m_viewOn;
CustomTypeEntryPtr m_targetLangApiType;
int m_revision = 0;
int m_sbkIndex = 0;
return false;
}
-TypeEntryPtr TypeEntry::viewOn() const
-{
- return m_d->m_viewOn;
-}
-
-void TypeEntry::setViewOn(const TypeEntryPtr &v)
-{
- m_d->m_viewOn = v;
-}
-
TypeEntry *TypeEntry::clone() const
{
return new TypeEntry(new TypeEntryPrivate(*m_d.data()));
m_d->m_version = source->m_d->m_version;
}
-// ----------------- CustomTypeEntry
-class CustomTypeEntryPrivate : public TypeEntryPrivate
+// ----------------- CppTypeEntry
+
+class CppTypeEntryPrivate : public TypeEntryPrivate
{
public:
using TypeEntryPrivate::TypeEntryPrivate;
+ CppTypeEntryCPtr m_viewOn;
+ QString m_defaultConstructor;
+ TypeSystem::CopyableFlag m_copyableFlag = TypeSystem::CopyableFlag::Unspecified;
+ bool m_copyableDetected = true;
+ TypeSystem::DefaultConstructibleFlag m_defaultConstructibleFlag = TypeSystem::DefaultConstructibleFlag::Unspecified;
+ bool m_defaultConstructibleDetected = true;
+ TypeSystem::MovableFlag m_movableFlag = TypeSystem::MovableFlag::Unspecified;
+ bool m_movableDetected = true;
+ TypeSystem::QtMetaTypeRegistration m_qtMetaTypeRegistration =
+ TypeSystem::QtMetaTypeRegistration::Unspecified;
+};
+
+CppTypeEntry::CppTypeEntry(const QString &entryName, Type t,
+ const QVersionNumber &vr,
+ const TypeEntryCPtr &parent) :
+ TypeEntry(new CppTypeEntryPrivate(entryName, t, vr, parent))
+{
+}
+
+CppTypeEntry::CppTypeEntry(CppTypeEntryPrivate *d) :
+ TypeEntry(d)
+{
+}
+
+const QString &CppTypeEntry::defaultConstructor() const
+{
+ S_D(const CppTypeEntry);
+ return d->m_defaultConstructor;
+}
+
+void CppTypeEntry::setDefaultConstructor(const QString& defaultConstructor)
+{
+ S_D(CppTypeEntry);
+ d->m_defaultConstructor = defaultConstructor;
+}
+
+CppTypeEntryCPtr CppTypeEntry::viewOn() const
+{
+ S_D(const CppTypeEntry);
+ return d->m_viewOn;
+}
+
+void CppTypeEntry::setViewOn(const CppTypeEntryCPtr &v)
+{
+ S_D(CppTypeEntry);
+ d->m_viewOn = v;
+}
+
+TypeSystem::DefaultConstructibleFlag CppTypeEntry::defaultConstructibleFlag() const
+{
+ S_D(const CppTypeEntry);
+ return d->m_defaultConstructibleFlag;
+}
+
+void CppTypeEntry::setDefaultConstructibleFlag(TypeSystem::DefaultConstructibleFlag flag)
+{
+ S_D(CppTypeEntry);
+ d->m_defaultConstructibleFlag = flag;
+}
+
+bool CppTypeEntry::isDefaultConstructible() const
+{
+ S_D(const CppTypeEntry);
+ switch (d->m_defaultConstructibleFlag) {
+ case TypeSystem::DefaultConstructibleFlag::Enabled:
+ return true;
+ case TypeSystem::DefaultConstructibleFlag::Disabled:
+ return false;
+ case TypeSystem::DefaultConstructibleFlag::Unspecified:
+ break;
+ }
+ return d->m_defaultConstructibleDetected || !d->m_defaultConstructor.isEmpty();
+}
+
+void CppTypeEntry::setDefaultConstructibleDetected(bool c)
+{
+ S_D(CppTypeEntry);
+ d->m_defaultConstructibleDetected = c;
+}
+
+TypeSystem::CopyableFlag CppTypeEntry::copyableFlag() const
+{
+ S_D(const CppTypeEntry);
+ return d->m_copyableFlag;
+}
+
+void CppTypeEntry::setCopyableFlag(TypeSystem::CopyableFlag flag)
+{
+ S_D(CppTypeEntry);
+ d->m_copyableFlag = flag;
+}
+
+bool CppTypeEntry::isCopyable() const
+{
+ S_D(const CppTypeEntry);
+ switch (d->m_copyableFlag) {
+ case TypeSystem::CopyableFlag::Enabled:
+ return true;
+ case TypeSystem::CopyableFlag::Disabled:
+ return false;
+ case TypeSystem::CopyableFlag::Unspecified:
+ break;
+ }
+ return d->m_copyableDetected;
+}
+
+void CppTypeEntry::setCopyableDetected(bool c)
+{
+ S_D(CppTypeEntry);
+ d->m_copyableDetected = c;
+}
+
+// Movable has no detection logic in the code model (yet). It is turned off for namespaces
+TypeSystem::MovableFlag CppTypeEntry::movableFlag() const
+{
+ S_D(const CppTypeEntry);
+ return d->m_movableFlag;
+}
+
+void CppTypeEntry::setMovableFlag(TypeSystem::MovableFlag flag)
+{
+ S_D(CppTypeEntry);
+ d->m_movableFlag = flag;
+}
+
+bool CppTypeEntry::isMovable() const
+{
+ S_D(const CppTypeEntry);
+ switch (d->m_movableFlag) {
+ case TypeSystem::MovableFlag::Enabled:
+ return true;
+ case TypeSystem::MovableFlag::Disabled:
+ return false;
+ case TypeSystem::MovableFlag::Unspecified:
+ break;
+ }
+ return d->m_movableDetected;
+}
+
+TypeSystem::QtMetaTypeRegistration CppTypeEntry::qtMetaTypeRegistration() const
+{
+ S_D(const CppTypeEntry);
+ return d->m_qtMetaTypeRegistration;
+}
+
+void CppTypeEntry::setQtMetaTypeRegistration(TypeSystem::QtMetaTypeRegistration r)
+{
+ S_D(CppTypeEntry);
+ d->m_qtMetaTypeRegistration = r;
+}
+
+TypeEntry *CppTypeEntry::clone() const
+{
+ S_D(const CppTypeEntry);
+ return new CppTypeEntry(new CppTypeEntryPrivate(*d));
+}
+
+// ----------------- CustomTypeEntry
+class CustomTypeEntryPrivate : public CppTypeEntryPrivate
+{
+public:
+ using CppTypeEntryPrivate::CppTypeEntryPrivate;
+
QString m_checkFunction;
};
CustomTypeEntry::CustomTypeEntry(const QString &entryName, const QVersionNumber &vr,
const TypeEntryCPtr &parent) :
- TypeEntry(new CustomTypeEntryPrivate(entryName, CustomType, vr, parent))
+ CppTypeEntry(new CustomTypeEntryPrivate(entryName, CustomType, vr, parent))
{
}
-CustomTypeEntry::CustomTypeEntry(TypeEntryPrivate *d) :
- TypeEntry(d)
+CustomTypeEntry::CustomTypeEntry(CustomTypeEntryPrivate *d) :
+ CppTypeEntry(d)
{
}
return d->m_cPythonType;
}
-PythonTypeEntry::PythonTypeEntry(TypeEntryPrivate *d) :
+PythonTypeEntry::PythonTypeEntry(CustomTypeEntryPrivate *d) :
CustomTypeEntry(d)
{
}
// ----------------- VoidTypeEntry
VoidTypeEntry::VoidTypeEntry() :
- TypeEntry(u"void"_s, VoidType, QVersionNumber(0, 0), nullptr)
+ CppTypeEntry(u"void"_s, VoidType, QVersionNumber(0, 0), nullptr)
{
}
-VoidTypeEntry::VoidTypeEntry(TypeEntryPrivate *d) :
- TypeEntry(d)
+VoidTypeEntry::VoidTypeEntry(CppTypeEntryPrivate *d) :
+ CppTypeEntry(d)
{
}
TypeEntry *VoidTypeEntry::clone() const
{
- return new VoidTypeEntry(new TypeEntryPrivate(*d_func()));
+ S_D(const CppTypeEntry);
+ return new VoidTypeEntry(new CppTypeEntryPrivate(*d));
}
VarargsTypeEntry::VarargsTypeEntry() :
}
// ----------------- ArrayTypeEntry
-class ArrayTypeEntryPrivate : public TypeEntryPrivate
+class ArrayTypeEntryPrivate : public CppTypeEntryPrivate
{
public:
explicit ArrayTypeEntryPrivate(const TypeEntryCPtr &nested_type, const QVersionNumber &vr,
const TypeEntryCPtr &parent) :
- TypeEntryPrivate(u"Array"_s, TypeEntry::ArrayType, vr, parent),
+ CppTypeEntryPrivate(u"Array"_s, TypeEntry::ArrayType, vr, parent),
m_nestedType(nested_type)
{
}
ArrayTypeEntry::ArrayTypeEntry(const TypeEntryCPtr &nested_type, const QVersionNumber &vr,
const TypeEntryCPtr &parent) :
- TypeEntry(new ArrayTypeEntryPrivate(nested_type, vr, parent))
+ CppTypeEntry(new ArrayTypeEntryPrivate(nested_type, vr, parent))
{
Q_ASSERT(nested_type);
}
}
ArrayTypeEntry::ArrayTypeEntry(ArrayTypeEntryPrivate *d) :
- TypeEntry(d)
+ CppTypeEntry(d)
{
}
// ----------------- PrimitiveTypeEntry
-class PrimitiveTypeEntryPrivate : public TypeEntryPrivate
+class PrimitiveTypeEntryPrivate : public CppTypeEntryPrivate
{
public:
PrimitiveTypeEntryPrivate(const QString &entryName, const QVersionNumber &vr,
const TypeEntryCPtr &parent) :
- TypeEntryPrivate(entryName, TypeEntry::PrimitiveType, vr, parent),
+ CppTypeEntryPrivate(entryName, TypeEntry::PrimitiveType, vr, parent),
m_preferredTargetLangType(true)
{
}
- QString m_defaultConstructor;
CustomConversionPtr m_customConversion;
PrimitiveTypeEntryPtr m_referencedTypeEntry;
uint m_preferredTargetLangType : 1;
PrimitiveTypeEntry::PrimitiveTypeEntry(const QString &entryName, const QVersionNumber &vr,
const TypeEntryCPtr &parent) :
- TypeEntry(new PrimitiveTypeEntryPrivate(entryName, vr, parent))
-{
-}
-
-QString PrimitiveTypeEntry::defaultConstructor() const
-{
- S_D(const PrimitiveTypeEntry);
- return d->m_defaultConstructor;
-}
-
-void PrimitiveTypeEntry::setDefaultConstructor(const QString &defaultConstructor)
-{
- S_D(PrimitiveTypeEntry);
- d->m_defaultConstructor = defaultConstructor;
-}
-
-bool PrimitiveTypeEntry::hasDefaultConstructor() const
+ CppTypeEntry(new PrimitiveTypeEntryPrivate(entryName, vr, parent))
{
- S_D(const PrimitiveTypeEntry);
- return !d->m_defaultConstructor.isEmpty();
}
PrimitiveTypeEntryPtr PrimitiveTypeEntry::referencedTypeEntry() const
return d->m_referencedTypeEntry;
}
-void PrimitiveTypeEntry::setReferencedTypeEntry(PrimitiveTypeEntryPtr referencedTypeEntry)
+void PrimitiveTypeEntry::setReferencedTypeEntry(const PrimitiveTypeEntryPtr &referencedTypeEntry)
{
S_D(PrimitiveTypeEntry);
d->m_referencedTypeEntry = referencedTypeEntry;
}
PrimitiveTypeEntry::PrimitiveTypeEntry(PrimitiveTypeEntryPrivate *d)
- : TypeEntry(d)
+ : CppTypeEntry(d)
{
}
// ----------------- ConfigurableTypeEntry
-class ConfigurableTypeEntryPrivate : public TypeEntryPrivate
+class ConfigurableTypeEntryPrivate : public CppTypeEntryPrivate
{
public:
- using TypeEntryPrivate::TypeEntryPrivate;
+ using CppTypeEntryPrivate::CppTypeEntryPrivate;
QString m_configCondition;
};
ConfigurableTypeEntry::ConfigurableTypeEntry(const QString &entryName, Type t,
const QVersionNumber &vr,
const TypeEntryCPtr &parent) :
- TypeEntry(new ConfigurableTypeEntryPrivate(entryName, t, vr, parent))
+ CppTypeEntry(new ConfigurableTypeEntryPrivate(entryName, t, vr, parent))
{
}
ConfigurableTypeEntry::ConfigurableTypeEntry(ConfigurableTypeEntryPrivate *d) :
- TypeEntry(d)
+ CppTypeEntry(d)
{
}
}
// ----------------- FlagsTypeEntry
-class FlagsTypeEntryPrivate : public TypeEntryPrivate
+class FlagsTypeEntryPrivate : public CppTypeEntryPrivate
{
public:
- using TypeEntryPrivate::TypeEntryPrivate;
+ using CppTypeEntryPrivate::CppTypeEntryPrivate;
QString m_originalName;
QString m_flagsName;
FlagsTypeEntry::FlagsTypeEntry(const QString &entryName, const QVersionNumber &vr,
const TypeEntryCPtr &parent) :
- TypeEntry(new FlagsTypeEntryPrivate(entryName, FlagsType, vr, parent))
+ CppTypeEntry(new FlagsTypeEntryPrivate(entryName, FlagsType, vr, parent))
{
}
}
FlagsTypeEntry::FlagsTypeEntry(FlagsTypeEntryPrivate *d) :
- TypeEntry(d)
+ CppTypeEntry(d)
{
}
FieldModificationList m_fieldMods;
QList<TypeSystemProperty> m_properties;
QList<TypeSystemPyMethodDefEntry> m_PyMethodDefEntrys;
- QString m_defaultConstructor;
QString m_defaultSuperclass;
QString m_qualifiedCppName;
QString m_docFile;
QString m_polymorphicNameFunction;
QString m_targetType;
ComplexTypeEntry::TypeFlags m_typeFlags;
- ComplexTypeEntry::CopyableFlag m_copyableFlag = ComplexTypeEntry::Unknown;
QString m_hashFunction;
ComplexTypeEntryCPtr m_baseContainerType;
TypeSystem::BoolCast m_isNullMode = TypeSystem::BoolCast::Unspecified;
TypeSystem::QtMetaTypeRegistration m_qtMetaTypeRegistration =
TypeSystem::QtMetaTypeRegistration::Unspecified;
- // Determined by AbstractMetaBuilder from the code model.
- bool m_isValueTypeWithCopyConstructorOnly = false;
};
ComplexTypeEntry::ComplexTypeEntry(const QString &entryName, TypeEntry::Type t,
d->m_deleteInMainThread = dmt;
}
-ComplexTypeEntry::CopyableFlag ComplexTypeEntry::copyable() const
-{
- S_D(const ComplexTypeEntry);
- return d->m_copyableFlag;
-}
-
-void ComplexTypeEntry::setCopyable(ComplexTypeEntry::CopyableFlag flag)
-{
- S_D(ComplexTypeEntry);
- d->m_copyableFlag = flag;
-}
-
-TypeSystem::QtMetaTypeRegistration ComplexTypeEntry::qtMetaTypeRegistration() const
-{
- S_D(const ComplexTypeEntry);
- return d->m_qtMetaTypeRegistration;
-}
-
-void ComplexTypeEntry::setQtMetaTypeRegistration(TypeSystem::QtMetaTypeRegistration r)
-{
- S_D(ComplexTypeEntry);
- d->m_qtMetaTypeRegistration = r;
-}
-
QString ComplexTypeEntry::hashFunction() const
{
S_D(const ComplexTypeEntry);
d->m_allowThread = allowThread;
}
-void ComplexTypeEntry::setDefaultConstructor(const QString& defaultConstructor)
-{
- S_D(ComplexTypeEntry);
- d->m_defaultConstructor = defaultConstructor;
-}
-
-QString ComplexTypeEntry::defaultConstructor() const
-{
- S_D(const ComplexTypeEntry);
- return d->m_defaultConstructor;
-}
-
-bool ComplexTypeEntry::hasDefaultConstructor() const
-{
- S_D(const ComplexTypeEntry);
- return !d->m_defaultConstructor.isEmpty();
-}
-
TypeSystem::SnakeCase ComplexTypeEntry::snakeCase() const
{
S_D(const ComplexTypeEntry);
d->m_snakeCase = sc;
}
-bool ComplexTypeEntry::isValueTypeWithCopyConstructorOnly() const
-{
- S_D(const ComplexTypeEntry);
- return d->m_isValueTypeWithCopyConstructorOnly;
-}
-
-void ComplexTypeEntry::setValueTypeWithCopyConstructorOnly(bool v)
-{
- S_D(ComplexTypeEntry);
- d->m_isValueTypeWithCopyConstructorOnly = v;
-}
-
QString ComplexTypeEntry::docFile() const
{
S_D(const ComplexTypeEntry);
return d->m_target;
}
-void TypedefEntry::setTarget(ComplexTypeEntryPtr target)
+void TypedefEntry::setTarget(const ComplexTypeEntryPtr &target)
{
S_D(TypedefEntry);
d->m_target = target;
QString m_nullCheckMethod;
QString m_resetMethod;
SmartPointerTypeEntry::Instantiations m_instantiations;
+ TypeEntryCList m_excludedInstantiations;
TypeSystem::SmartPointerType m_smartPointerType;
};
ComplexTypeEntry(new SmartPointerTypeEntryPrivate(entryName, getterName, smartPointerType,
refCountMethodName, vr, parent))
{
+ S_D(SmartPointerTypeEntry);
+ // Pre-set detection values which is not done by the code model since
+ // the clang parser might not see all system library includes.
+ d->m_defaultConstructibleDetected = smartPointerType != TypeSystem::SmartPointerType::Handle;
+ d->m_copyableDetected = smartPointerType != TypeSystem::SmartPointerType::Unique;
}
TypeSystem::SmartPointerType SmartPointerTypeEntry::smartPointerType() const
d->m_instantiations = i;
}
+void SmartPointerTypeEntry::setExcludedInstantiations(const TypeEntryCList &ex)
+{
+ S_D(SmartPointerTypeEntry);
+ d->m_excludedInstantiations = ex;
+}
+
+const TypeEntryCList &SmartPointerTypeEntry::excludedInstantiations() const
+{
+ S_D(const SmartPointerTypeEntry);
+ return d->m_excludedInstantiations;
+}
+
SmartPointerTypeEntry::SmartPointerTypeEntry(SmartPointerTypeEntryPrivate *d) :
ComplexTypeEntry(d)
{
{
S_D(const SmartPointerTypeEntry);
// No instantiations specified, or match
- return d->m_instantiations.isEmpty() || d->instantiationIndex(e) != -1;
+ return !d->m_excludedInstantiations.contains(e)
+ && (d->m_instantiations.isEmpty() || d->instantiationIndex(e) != -1);
}
static QString fixSmartPointerName(QString name)
const TypeEntryCPtr &parent) :
ComplexTypeEntry(new NamespaceTypeEntryPrivate(entryName, NamespaceType, vr, parent))
{
+ S_D(NamespaceTypeEntry);
+ d->m_copyableDetected = d->m_defaultConstructibleDetected = d->m_movableDetected = false;
}
TypeEntry *NamespaceTypeEntry::clone() const
const TypeEntryCPtr &parent)
: ComplexTypeEntry(entryName, ObjectType, vr, parent)
{
+ S_D(ComplexTypeEntry);
+ d->m_defaultConstructibleDetected = d->m_copyableDetected = false;
}
TypeEntry *ObjectTypeEntry::clone() const
FORMAT_NONEMPTY_STRING("package", m_d->m_targetLangPackage)
FORMAT_BOOL("stream", m_d->m_stream)
FORMAT_BOOL("built-in", m_d->m_builtin)
- if (m_d->m_viewOn)
- debug << ", views=" << m_d->m_viewOn->name();
if (!m_d->m_version.isNull() && m_d->m_version > QVersionNumber(0, 0))
debug << ", version=" << m_d->m_version;
if (m_d->m_revision)
formatList(debug, "extraIncludes", m_d->m_extraIncludes, ", ");
}
+void CppTypeEntry::formatDebug(QDebug &debug) const
+{
+ S_D(const CppTypeEntry);
+ if (d->m_viewOn)
+ debug << ", views=" << d->m_viewOn->name();
+ if (isDefaultConstructible())
+ debug << ", [default constructible]";
+ if (isCopyable())
+ debug << ", [copyable]";
+ if (isMovable())
+ debug << ", [movable]";
+}
+
void PrimitiveTypeEntry::formatDebug(QDebug &debug) const
{
TypeEntry::formatDebug(debug);
FORMAT_BOOL("deleteInMainThread", d->m_deleteInMainThread)
if (d->m_typeFlags != 0)
debug << ", typeFlags=" << d->m_typeFlags;
- debug << ", copyableFlag=" << d->m_copyableFlag
- << ", except=" << int(d->m_exceptionHandling)
+ debug << ", except=" << int(d->m_exceptionHandling)
<< ", snakeCase=" << int(d->m_snakeCase);
FORMAT_NONEMPTY_STRING("defaultSuperclass", d->m_defaultSuperclass)
FORMAT_NONEMPTY_STRING("polymorphicIdValue", d->m_polymorphicIdValue)
#include "typesystem_typedefs.h"
#include <QtCore/qobjectdefs.h>
-#include <QtCore/QString>
-#include <QtCore/QScopedPointer>
+#include <QtCore/qstring.h>
+#include <QtCore/qscopedpointer.h>
class AbstractMetaType;
class CustomTypeEntry;
QVersionNumber version() const;
- // View on: Type to use for function argument conversion, fex
- // std::string_view -> std::string for foo(std::string_view).
- // cf AbstractMetaType::viewOn()
- TypeEntryPtr viewOn() const;
- void setViewOn(const TypeEntryPtr &v);
-
virtual TypeEntry *clone() const;
void useAsTypedef(const TypeEntryCPtr &source);
IntFlag
};
+enum class DefaultConstructibleFlag : unsigned char {
+ Unspecified,
+ Enabled,
+ Disabled
+};
+
+enum class CopyableFlag : unsigned char {
+ Unspecified,
+ Enabled,
+ Disabled
+};
+
+enum class MovableFlag : unsigned char {
+ Unspecified,
+ Enabled,
+ Disabled
+};
+
enum : int { OverloadNumberUnset = -1, OverloadNumberDefault = 99999 };
} // namespace TypeSystem
#ifndef TYPESYSTEM_TYPEDEFS_H
#define TYPESYSTEM_TYPEDEFS_H
-#include <QtCore/QList>
+#include <QtCore/qlist.h>
#include <memory>
class ConfigurableTypeEntry;
class ConstantValueTypeEntry;
class ContainerTypeEntry;
+class CppTypeEntry;
class CustomTypeEntry;
class EnumTypeEntry;
class EnumValueTypeEntry;
using ConfigurableTypeEntryPtr = std::shared_ptr<ConfigurableTypeEntry>;
using ConstantValueTypeEntryPtr = std::shared_ptr<ConstantValueTypeEntry>;
using ContainerTypeEntryPtr = std::shared_ptr<ContainerTypeEntry>;
+using CppTypeEntryPtr = std::shared_ptr<CppTypeEntry>;
using CustomTypeEntryPtr = std::shared_ptr<CustomTypeEntry>;
using EnumTypeEntryPtr = std::shared_ptr<EnumTypeEntry>;
using EnumValueTypeEntryPtr = std::shared_ptr<EnumValueTypeEntry>;
using ConstantValueTypeEntryCPtr = std::shared_ptr<const ConstantValueTypeEntry>;
using ConfigurableTypeEntryCPtr = std::shared_ptr<const ConfigurableTypeEntry>;
using ContainerTypeEntryCPtr = std::shared_ptr<const ContainerTypeEntry>;
+using CppTypeEntryCPtr = std::shared_ptr<const CppTypeEntry>;
using CustomTypeEntryCPtr = std::shared_ptr<const CustomTypeEntry>;
using EnumTypeEntryCPtr = std::shared_ptr<const EnumTypeEntry>;
using EnumValueTypeEntryCPtr = std::shared_ptr<const EnumValueTypeEntry>;
#include "customconversion.h"
#include "customtypenentry.h"
#include "documentation_enums.h"
+#include "filecache.h"
#include "flagstypeentry.h"
#include "functiontypeentry.h"
#include "namespacetypeentry.h"
#include "qtcompat.h"
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QFileInfo>
-#include <QtCore/QRegularExpression>
-#include <QtCore/QSet>
-#include <QtCore/QStringView>
-#include <QtCore/QStringAlgorithms>
-#include <QtCore/QVersionNumber>
-#include <QtCore/QXmlStreamAttributes>
-#include <QtCore/QXmlStreamReader>
-#include <QtCore/QXmlStreamEntityResolver>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qset.h>
+#include <QtCore/qstringview.h>
+#include <QtCore/qstringalgorithms.h>
+#include <QtCore/qversionnumber.h>
+#include <QtCore/qxmlstream.h>
#include <algorithm>
#include <optional>
constexpr auto allowThreadAttribute = "allow-thread"_L1;
constexpr auto checkFunctionAttribute = "check-function"_L1;
+constexpr auto defaultConstructibleAttribute = "default-constructible"_L1;
constexpr auto copyableAttribute = "copyable"_L1;
+constexpr auto movableAttribute = "movable"_L1;
constexpr auto accessAttribute = "access"_L1;
constexpr auto actionAttribute = "action"_L1;
constexpr auto quoteAfterLineAttribute = "quote-after-line"_L1;
return attributes->hasAttribute(fileAttribute);
}
-// Extract a snippet from a file within annotation "// @snippet label".
-std::optional<QString>
- extractSnippet(const QString &code, const QString &snippetLabel)
+static QRegularExpression snippetPattern(const QString &snippetLabel)
{
- if (snippetLabel.isEmpty())
- return code;
const QString pattern = R"(^\s*//\s*@snippet\s+)"_L1
- + QRegularExpression::escape(snippetLabel)
- + R"(\s*$)"_L1;
- const QRegularExpression snippetRe(pattern);
- Q_ASSERT(snippetRe.isValid());
-
- bool useLine = false;
- bool foundLabel = false;
- QString result;
- const auto lines = QStringView{code}.split(u'\n');
- for (const auto &line : lines) {
- if (snippetRe.matchView(line).hasMatch()) {
- foundLabel = true;
- useLine = !useLine;
- if (!useLine)
- break; // End of snippet reached
- } else if (useLine)
- result += line.toString() + u'\n';
- }
- if (!foundLabel)
- return {};
- return CodeSnipAbstract::fixSpaces(result);
+ + QRegularExpression::escape(snippetLabel)
+ + R"(\s*$)"_L1;
+ QRegularExpression result(pattern);
+ Q_ASSERT(result.isValid());
+ return result;
}
template <class EnumType>
};
ENUM_LOOKUP_LINEAR_SEARCH
+ENUM_LOOKUP_BEGIN(DocumentationTarget, Qt::CaseSensitive,
+ docTargetFromAttribute)
+ {
+ {u"documentation", DocumentationTarget::Documentation},
+ {u"docstring", DocumentationTarget::DocString}
+ };
+ENUM_LOOKUP_LINEAR_SEARCH
+
ENUM_LOOKUP_BEGIN(ContainerTypeEntry::ContainerKind, Qt::CaseSensitive,
containerTypeFromAttribute)
{
{u"no-null-pointer", StackElement::NoNullPointers},
{u"object-type", StackElement::ObjectTypeEntry},
{u"opaque-container", StackElement::OpaqueContainer},
+ {u"overload-removal", StackElement::OverloadRemoval},
{u"parent", StackElement::ParentOwner},
{u"primitive-type", StackElement::PrimitiveTypeEntry},
{u"property", StackElement::Property},
};
ENUM_LOOKUP_LINEAR_SEARCH
-static int indexOfAttribute(const QXmlStreamAttributes &atts,
- QAnyStringView name)
+static qsizetype indexOfAttribute(const QXmlStreamAttributes &atts,
+ QAnyStringView name)
{
for (qsizetype i = 0, size = atts.size(); i < size; ++i) {
if (atts.at(i).qualifiedName() == name)
m_templateEntry = nullptr;
break;
case StackElement::InsertTemplate:
- if (auto *snip = injectCodeTarget(1))
- snip->addTemplateInstance(m_templateInstance);
+ if (auto *snip = injectCodeTarget(1)) {
+ Q_ASSERT(m_templateInstance.has_value());
+ snip->addTemplateInstance(m_templateInstance.value());
+ }
m_templateInstance.reset();
break;
return false;
}
-static TypeEntryPtr findViewedType(const QString &name)
+static CppTypeEntryCPtr findViewedType(const QString &name)
{
const auto range = TypeDatabase::instance()->entries().equal_range(name);
for (auto i = range.first; i != range.second; ++i) {
case TypeEntry::PrimitiveType:
case TypeEntry::ContainerType:
case TypeEntry::ObjectType:
- return i.value();
+ return std::dynamic_pointer_cast<const CppTypeEntry>(i.value());
default:
break;
}
const auto name = attributes->at(i).qualifiedName();
if (name == u"revision") {
type->setRevision(attributes->takeAt(i).value().toInt());
+ }
+ }
+ return true;
+}
+
+bool TypeSystemParser::applyCppAttributes(const ConditionalStreamReader &reader,
+ const CppTypeEntryPtr &type,
+ QXmlStreamAttributes *attributes)
+{
+ if (!applyCommonAttributes(reader, type, attributes))
+ return false;
+ for (auto i = attributes->size() - 1; i >= 0; --i) {
+ const auto name = attributes->at(i).qualifiedName();
+ if (name == u"default-constructor") {
+ type->setDefaultConstructor(attributes->takeAt(i).value().toString());
} else if (name == u"view-on") {
const QString name = attributes->takeAt(i).value().toString();
- TypeEntryPtr views = findViewedType(name);
+ auto views = findViewedType(name);
if (!views) {
m_error = msgCannotFindView(name, type->name());
return false;
}
type->setViewOn(views);
+ } else if (name == defaultConstructibleAttribute) {
+ const bool v = convertBoolean(attributes->takeAt(i).value(),
+ defaultConstructibleAttribute, false);
+ type->setDefaultConstructibleFlag(v ? TypeSystem::DefaultConstructibleFlag::Enabled
+ : TypeSystem::DefaultConstructibleFlag::Disabled);
+ } else if (name == copyableAttribute) {
+ const bool v = convertBoolean(attributes->takeAt(i).value(),
+ copyableAttribute, false);
+ type->setCopyableFlag(v ? TypeSystem::CopyableFlag::Enabled
+ : TypeSystem::CopyableFlag::Disabled);
+ } else if (name == movableAttribute) {
+ const bool v = convertBoolean(attributes->takeAt(i).value(),
+ movableAttribute, false);
+ type->setMovableFlag(v ? TypeSystem::MovableFlag::Enabled
+ : TypeSystem::MovableFlag::Disabled);
+ } else if (name == qtMetaTypeAttribute) {
+ const auto attribute = attributes->takeAt(i);
+ const auto qtMetaTypeOpt = qtMetaTypeFromAttribute(attribute.value());
+ if (qtMetaTypeOpt.has_value()) {
+ type->setQtMetaTypeRegistration(qtMetaTypeOpt.value());
+ } else {
+ qCWarning(lcShiboken, "%s",
+ qPrintable(msgInvalidAttributeValue(attribute)));
+ }
}
}
return true;
}
ftype->setOriginalName(flagName);
- if (!applyCommonAttributes(reader, ftype, attributes))
+ if (!applyCppAttributes(reader, ftype, attributes))
return nullptr;
QStringList lst = flagName.split(u"::"_s);
m_context->db->addFlagsType(ftype);
m_context->db->addType(ftype);
- const int revisionIndex =
- indexOfAttribute(*attributes, u"flags-revision");
+ const auto revisionIndex = indexOfAttribute(*attributes, u"flags-revision");
ftype->setRevision(revisionIndex != -1
? attributes->takeAt(revisionIndex).value().toInt()
: enumEntry->revision());
QString valueCheckMethod;
QString nullCheckMethod;
QString resetMethod;
+ TypeDatabaseParserContext::SmartPointerEntry entry;
QString instantiations;
+ QString excludedInstantiations;
for (auto i = attributes->size() - 1; i >= 0; --i) {
const auto name = attributes->at(i).qualifiedName();
if (name == u"type") {
} else if (name == u"ref-count-method") {
refCountMethodName = attributes->takeAt(i).value().toString();
} else if (name == u"instantiations") {
- instantiations = attributes->takeAt(i).value().toString();
+ entry.instantiations = attributes->takeAt(i).value().toString();
+ } else if (name == u"excluded-instantiations") {
+ entry.excludedInstantiations = attributes->takeAt(i).value().toString();
} else if (name == u"value-check-method") {
valueCheckMethod = attributes->takeAt(i).value().toString();
} else if (name == u"null-check-method") {
auto type = std::make_shared<SmartPointerTypeEntry>(name, getter, smartPointerType,
refCountMethodName, since,
currentParentTypeEntry());
- if (!applyCommonAttributes(reader, type, attributes))
+ if (!applyComplexTypeAttributes(reader, type, attributes))
return nullptr;
- applyComplexTypeAttributes(reader, type, attributes);
type->setNullCheckMethod(nullCheckMethod);
type->setValueCheckMethod(valueCheckMethod);
type->setResetMethod(resetMethod);
- m_context->smartPointerInstantiations.insert(type, instantiations);
+ if (!entry.instantiations.isEmpty() || !entry.excludedInstantiations.isEmpty())
+ m_context->smartPointerInstantiations.insert(type, entry);
return type;
}
return nullptr;
auto type = std::make_shared<PrimitiveTypeEntry>(name, since, currentParentTypeEntry());
QString targetLangApiName;
- if (!applyCommonAttributes(reader, type, attributes))
+ if (!applyCppAttributes(reader, type, attributes))
return nullptr;
for (auto i = attributes->size() - 1; i >= 0; --i) {
const auto name = attributes->at(i).qualifiedName();
const bool v = convertBoolean(attributes->takeAt(i).value(),
preferredTargetLangTypeAttribute, true);
type->setPreferredTargetLangType(v);
- } else if (name == u"default-constructor") {
- type->setDefaultConstructor(attributes->takeAt(i).value().toString());
}
}
attributes->removeAt(typeIndex);
auto type = std::make_shared<ContainerTypeEntry>(name, containerTypeOpt.value(),
since, currentParentTypeEntry());
- if (!applyCommonAttributes(reader, type, attributes))
+ if (!applyComplexTypeAttributes(reader, type, attributes))
return nullptr;
- applyComplexTypeAttributes(reader, type, attributes);
for (auto i = attributes->size() - 1; i >= 0; --i) {
const auto name = attributes->at(i).qualifiedName();
if (!checkRootElement())
return nullptr;
auto entry = std::make_shared<EnumTypeEntry>(name, since, currentParentTypeEntry());
- applyCommonAttributes(reader, entry, attributes);
+ if (!applyCppAttributes(reader, entry, attributes))
+ return nullptr;
entry->setTargetLangPackage(m_defaultPackage);
QString flagNames;
return nullptr;
auto result = std::make_shared<NamespaceTypeEntry>(name, since, currentParentTypeEntry());
auto visibility = TypeSystem::Visibility::Unspecified;
- applyCommonAttributes(reader, result, attributes);
for (auto i = attributes->size() - 1; i >= 0; --i) {
const auto attributeName = attributes->at(i).qualifiedName();
if (attributeName == u"files") {
if (visibility != TypeSystem::Visibility::Unspecified)
result->setVisibility(visibility);
// Handle legacy "generate" before the common handling
- applyComplexTypeAttributes(reader, result, attributes);
+ if (!applyComplexTypeAttributes(reader, result, attributes))
+ return {};
if (result->extends() && !result->hasPattern()) {
m_error = msgExtendingNamespaceRequiresPattern(name);
if (!checkRootElement())
return nullptr;
auto typeEntry = std::make_shared<ValueTypeEntry>(name, since, currentParentTypeEntry());
- if (!applyCommonAttributes(reader, typeEntry, attributes))
+ if (!applyComplexTypeAttributes(reader, typeEntry, attributes))
return nullptr;
- applyComplexTypeAttributes(reader, typeEntry, attributes);
- const int defaultCtIndex =
- indexOfAttribute(*attributes, u"default-constructor");
- if (defaultCtIndex != -1)
- typeEntry->setDefaultConstructor(attributes->takeAt(defaultCtIndex).value().toString());
return typeEntry;
}
const QString sourceType = attributes->takeAt(sourceIndex).value().toString();
auto result = std::make_shared<TypedefEntry>(name, sourceType, since,
currentParentTypeEntry());
- if (!applyCommonAttributes(reader, result, attributes))
+ if (!applyComplexTypeAttributes(reader, result, attributes))
return nullptr;
- applyComplexTypeAttributes(reader, result, attributes);
return result;
}
-void TypeSystemParser::applyComplexTypeAttributes(const ConditionalStreamReader &reader,
- const ComplexTypeEntryPtr &ctype,
- QXmlStreamAttributes *attributes) const
+bool TypeSystemParser::applyComplexTypeAttributes(const ConditionalStreamReader &reader,
+ const ComplexTypeEntryPtr &ctype,
+ QXmlStreamAttributes *attributes)
{
+ if (!applyCppAttributes(reader, ctype, attributes))
+ return false;
bool generate = true;
- ctype->setCopyable(ComplexTypeEntry::Unknown);
auto exceptionHandling = m_exceptionHandling;
auto allowThread = m_allowThread;
ctype->setPolymorphicNameFunction(attributes->takeAt(i).value().toString());
} else if (name == u"polymorphic-id-expression") {
ctype->setPolymorphicIdValue(attributes->takeAt(i).value().toString());
- } else if (name == copyableAttribute) {
- const bool v = convertBoolean(attributes->takeAt(i).value(),
- copyableAttribute, false);
- ctype->setCopyable(v ? ComplexTypeEntry::CopyableSet : ComplexTypeEntry::NonCopyableSet);
} else if (name == exceptionHandlingAttribute) {
const auto attribute = attributes->takeAt(i);
const auto exceptionOpt = exceptionHandlingFromAttribute(attribute.value());
qCWarning(lcShiboken, "%s",
qPrintable(msgInvalidAttributeValue(attribute)));
}
- } else if (name == qtMetaTypeAttribute) {
- const auto attribute = attributes->takeAt(i);
- const auto qtMetaTypeOpt = qtMetaTypeFromAttribute(attribute.value());
- if (qtMetaTypeOpt.has_value()) {
- ctype->setQtMetaTypeRegistration(qtMetaTypeOpt.value());
- } else {
- qCWarning(lcShiboken, "%s",
- qPrintable(msgInvalidAttributeValue(attribute)));
- }
} else if (name == parentManagementAttribute) {
const auto attribute = attributes->takeAt(i);
if (convertBoolean(attribute.value(), parentManagementAttribute, false))
ctype->setCodeGeneration(m_generate);
else
ctype->setCodeGeneration(TypeEntry::GenerationDisabled);
+ return true;
}
bool TypeSystemParser::parseConfiguration(StackElement topElement,
return true;
}
+bool TypeSystemParser::parseOverloadRemoval(StackElement topElement,
+ QXmlStreamAttributes *attributes)
+{
+ if (topElement != StackElement::Root) {
+ m_error = u"<overload-removal> can only appear under the root element."_s;
+ return false;
+ }
+ OverloadRemovalRule rule;
+ for (auto i = attributes->size() - 1; i >= 0; --i) {
+ const auto name = attributes->at(i).qualifiedName();
+ if (name == u"type") {
+ rule.type = attributes->takeAt(i).value().toString();
+ } else if (name == u"replaces") {
+ rule.redundantTypes = attributes->takeAt(i).value().toString().split(u';');
+ }
+ }
+ if (rule.type.isEmpty() || rule.redundantTypes.isEmpty()) {
+ m_error = u"<overload-removal> requires \"type\" and \"replaces\" attributes."_s;
+ return false;
+ }
+ TypeDatabase::instance()->addOverloadRemovalRule(rule);
+ return true;
+}
+
bool TypeSystemParser::parseRenameFunction(const ConditionalStreamReader &,
QString *name, QXmlStreamAttributes *attributes)
{
TypeSystem::DocModificationMode mode = TypeSystem::DocModificationReplace;
DocumentationFormat format = DocumentationFormat::Native;
DocumentationEmphasis emphasis = DocumentationEmphasis::None;
+ DocumentationTarget target = DocumentationTarget::Documentation;
+
for (auto i = attributes->size() - 1; i >= 0; --i) {
const auto name = attributes->at(i).qualifiedName();
if (name == u"mode") {
return false;
}
emphasis = emphasisOpt.value();
+ } else if (name == u"target") {
+ const auto attribute = attributes->takeAt(i);
+ const auto targetOpt = docTargetFromAttribute(attribute.value());
+ if (!targetOpt.has_value()) {
+ m_error = msgInvalidAttributeValue(attribute);
+ return false;
+ }
+ target = targetOpt.value();
}
}
return false;
}
+ if (target == DocumentationTarget::DocString && mode != TypeSystem::DocModificationReplace) {
+ m_error = "Doc strings only support \"replace\""_L1;
+ return false;
+ }
+
QString signature = isTypeEntry(topElement) ? QString() : m_currentSignature;
DocModification mod(mode, signature);
mod.setFormat(format);
mod.setEmphasis(emphasis);
+ mod.setTarget(target);
if (hasFileSnippetAttributes(attributes)) {
const auto snippetOptional = readFileSnippet(attributes);
if (!snippetOptional.has_value())
std::const_pointer_cast<TypeSystemTypeEntry>(m_context->db->findTypeSystemType(m_defaultPackage));
const bool add = !moduleEntry;
if (add) {
- moduleEntry.reset(new TypeSystemTypeEntry(m_defaultPackage, since,
- currentParentTypeEntry()));
+ moduleEntry = std::make_shared<TypeSystemTypeEntry>(m_defaultPackage, since,
+ currentParentTypeEntry());
moduleEntry->setSubModule(subModuleOf);
}
if (!docPackage.isEmpty())
if (lang != TypeSystem::TargetLangCode)
return true;
- QFile conversionSource(sourceFile);
- if (!conversionSource.open(QIODevice::ReadOnly | QIODevice::Text)) {
- m_error = msgCannotOpenForReading(conversionSource);
- return false;
- }
- const auto conversionRuleOptional =
- extractSnippet(QString::fromUtf8(conversionSource.readAll()), snippetLabel);
- if (!conversionRuleOptional.has_value()) {
- m_error = msgCannotFindSnippet(sourceFile, snippetLabel);
+ const auto conversionRuleOptional = readFileSnippetContents(sourceFile, snippetLabel);
+ if (!conversionRuleOptional.has_value())
return false;
- }
valueTypeEntry->setTargetConversionRule(conversionRuleOptional.value());
}
return true;
return false;
}
- int idx;
+ int idx = 0;
if (!parseArgumentIndex(index, &idx, &m_error))
return false;
ArgumentModification &lastArgMod = m_contextStack.top()->functionMods.last().argument_mods().last();
lastArgMod.setNoNullPointers(true);
- const int defaultValueIndex =
- indexOfAttribute(*attributes, u"default-value");
+ const auto defaultValueIndex = indexOfAttribute(*attributes, u"default-value");
if (defaultValueIndex != -1) {
const QXmlStreamAttribute attribute = attributes->takeAt(defaultValueIndex);
qCWarning(lcShiboken, "%s",
return true;
}
+std::optional<QString>
+ TypeSystemParser::readFileSnippetContents(const QString &fileName,
+ const QString &snippetName)
+{
+ static FileCache cache;
+
+ const auto result = snippetName.isEmpty() ? cache.fileContents(fileName)
+ : cache.fileSnippet(fileName, snippetName, snippetPattern(snippetName));
+
+ if (!result.has_value())
+ m_error = cache.errorString();
+ return result;
+}
+
std::optional<TypeSystemParser::Snippet>
TypeSystemParser::readFileSnippet(QXmlStreamAttributes *attributes)
{
}
const QString resolved = m_context->db->modifiedTypesystemFilepath(result.fileName,
m_currentPath);
- if (!QFile::exists(resolved)) {
- m_error = u"File for inject code not exist: "_s
- + QDir::toNativeSeparators(result.fileName);
+ auto snippetO = readFileSnippetContents(resolved, result.snippetLabel);
+ if (!snippetO.has_value())
return std::nullopt;
- }
- QFile codeFile(resolved);
- if (!codeFile.open(QIODevice::Text | QIODevice::ReadOnly)) {
- m_error = msgCannotOpenForReading(codeFile);
- return std::nullopt;
- }
- const auto contentOptional = extractSnippet(QString::fromUtf8(codeFile.readAll()),
- result.snippetLabel);
- codeFile.close();
- if (!contentOptional.has_value()) {
- m_error = msgCannotFindSnippet(resolved, result.snippetLabel);
- return std::nullopt;
- }
- result.content = contentOptional.value();
+ result.content = snippetO.value();
return result;
}
return true;
}
-TemplateInstance *
+TemplateEntryPtr TypeSystemParser::parseTemplate(QXmlStreamAttributes *attributes)
+{
+ auto result = std::make_shared<TemplateEntry>();
+ if (hasFileSnippetAttributes(attributes)) {
+ const auto snippetOptional = readFileSnippet(attributes);
+ if (!snippetOptional.has_value())
+ return {};
+ result->addCode(snippetOptional.value().content);
+ }
+ for (auto i = attributes->size() - 1; i >= 0; --i) {
+ const auto name = attributes->at(i).qualifiedName();
+ if (name == nameAttribute)
+ result->setName(attributes->takeAt(i).value().toString());
+ }
+ if (result->name().isEmpty()) {
+ m_error = msgMissingAttribute(nameAttribute);
+ return {};
+ }
+ return result;
+}
+
+std::optional<TemplateInstance>
TypeSystemParser::parseInsertTemplate(const ConditionalStreamReader &,
StackElement topElement,
QXmlStreamAttributes *attributes)
(topElement != StackElement::ConversionRule)) {
m_error = u"Can only insert templates into code snippets, templates, "\
"conversion-rule, native-to-target or add-conversion tags."_s;
- return nullptr;
+ return std::nullopt;
}
const auto nameIndex = indexOfAttribute(*attributes, nameAttribute);
if (nameIndex == -1) {
m_error = msgMissingAttribute(nameAttribute);
- return nullptr;
+ return std::nullopt;
}
- return new TemplateInstance(attributes->takeAt(nameIndex).value().toString());
+ return TemplateInstance(attributes->takeAt(nameIndex).value().toString());
}
bool TypeSystemParser::parseReplace(const ConditionalStreamReader &,
return false;
auto ce = std::make_shared<ObjectTypeEntry>(name, versionRange.since, currentParentTypeEntry());
top->entry = ce;
- applyCommonAttributes(reader, top->entry, &attributes);
- applyComplexTypeAttributes(reader, ce, &attributes);
+ if (!applyComplexTypeAttributes(reader, ce, &attributes))
+ return false;
}
break;
case StackElement::FunctionTypeEntry:
|| element == StackElement::AddFunction
|| element == StackElement::DeclareFunction
|| element == StackElement::Template
- || element == StackElement::OpaqueContainer;
+ || element == StackElement::OpaqueContainer
+ || element == StackElement::OverloadRemoval;
if (!topLevel && m_stack.at(m_stack.size() - 2) == StackElement::Root) {
m_error = u"Tag requires parent: '"_s + tagName.toString() + u'\'';
if (!parseSystemInclude(reader, &attributes))
return false;
break;
- case StackElement::Template: {
- const auto nameIndex = indexOfAttribute(attributes, nameAttribute);
- if (nameIndex == -1) {
- m_error = msgMissingAttribute(nameAttribute);
+ case StackElement::Template:
+ m_templateEntry = parseTemplate(&attributes);
+ if (m_templateEntry == nullptr)
return false;
- }
- m_templateEntry.reset(new TemplateEntry(attributes.takeAt(nameIndex).value().toString()));
- }
break;
case StackElement::InsertTemplate:
- m_templateInstance.reset(parseInsertTemplate(reader, topElement, &attributes));
- if (!m_templateInstance)
+ m_templateInstance = parseInsertTemplate(reader, topElement, &attributes);
+ if (!m_templateInstance.has_value())
return false;
break;
case StackElement::Replace:
if (!parseConfiguration(topElement, &attributes))
return false;
break;
+ case StackElement::OverloadRemoval:
+ if (!parseOverloadRemoval(topElement, &attributes))
+ return false;
+ break;
default:
break; // nada
}
#include "typesystem_typedefs.h"
#include "codesnip.h"
-#include <QtCore/QStack>
-#include <QtCore/QHash>
-#include <QtCore/QScopedPointer>
+#include <QtCore/qstack.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qscopedpointer.h>
#include <memory>
#include <optional>
ImportFile,
OpaqueContainer,
Configuration,
+ OverloadRemoval,
Unimplemented
};
bool applyCommonAttributes(const ConditionalStreamReader &reader,
const TypeEntryPtr &type,
QXmlStreamAttributes *attributes);
+ bool applyCppAttributes(const ConditionalStreamReader &reader,
+ const CppTypeEntryPtr &type,
+ QXmlStreamAttributes *attributes);
PrimitiveTypeEntryPtr
parsePrimitiveTypeEntry(const ConditionalStreamReader &, const QString &name,
const QVersionNumber &since, QXmlStreamAttributes *);
parseTypedefEntry(const ConditionalStreamReader &, const QString &name,
StackElement topElement,
const QVersionNumber &since, QXmlStreamAttributes *);
- void applyComplexTypeAttributes(const ConditionalStreamReader &, const ComplexTypeEntryPtr &ctype,
- QXmlStreamAttributes *) const;
+ bool applyComplexTypeAttributes(const ConditionalStreamReader &, const ComplexTypeEntryPtr &ctype,
+ QXmlStreamAttributes *);
bool parseConfiguration(StackElement topElement,
QXmlStreamAttributes *attributes);
+ bool parseOverloadRemoval(StackElement topElement, QXmlStreamAttributes *attributes);
bool parseRenameFunction(const ConditionalStreamReader &, QString *name,
QXmlStreamAttributes *);
bool parseInjectDocumentation(const ConditionalStreamReader &, StackElement topElement,
QXmlStreamAttributes *);
bool parseParentOwner(const ConditionalStreamReader &, StackElement topElement,
QXmlStreamAttributes *);
+ std::optional<QString>
+ readFileSnippetContents(const QString &fileName, const QString &snippetName);
std::optional<Snippet> readFileSnippet(QXmlStreamAttributes *attributes);
bool readCodeSnippet(QXmlStreamAttributes *attributes, CodeSnip *snip);
bool parseInjectCode(const ConditionalStreamReader &, StackElement topElement, QXmlStreamAttributes *);
bool parseInclude(const ConditionalStreamReader &, StackElement topElement,
const TypeEntryPtr &entry, QXmlStreamAttributes *);
bool parseSystemInclude(const ConditionalStreamReader &, QXmlStreamAttributes *);
- TemplateInstance
- *parseInsertTemplate(const ConditionalStreamReader &, StackElement topElement,
- QXmlStreamAttributes *);
+ TemplateEntryPtr parseTemplate(QXmlStreamAttributes *attributes);
+ std::optional<TemplateInstance>
+ parseInsertTemplate(const ConditionalStreamReader &, StackElement topElement,
+ QXmlStreamAttributes *);
bool parseReplace(const ConditionalStreamReader &, StackElement topElement,
QXmlStreamAttributes *);
bool checkDuplicatedTypeEntry(const ConditionalStreamReader &reader,
const TypeEntry::CodeGeneration m_generate;
EnumTypeEntryPtr m_currentEnum;
- TemplateInstancePtr m_templateInstance;
+ std::optional<TemplateInstance> m_templateInstance;
TemplateEntryPtr m_templateEntry;
ContextStack m_contextStack;
#ifndef VOIDTYPEENTRY_H
#define VOIDTYPEENTRY_H
-#include "typesystem.h"
+#include "cpptypeentry.h"
-class VoidTypeEntry : public TypeEntry
+class VoidTypeEntry : public CppTypeEntry
{
public:
VoidTypeEntry();
TypeEntry *clone() const override;
protected:
- explicit VoidTypeEntry(TypeEntryPrivate *d);
+ explicit VoidTypeEntry(CppTypeEntryPrivate *d);
};
#endif // VOIDTYPEENTRY_H
#ifndef XMLUTILS_H
#define XMLUTILS_H
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
#include <memory>
#include "qtcompat.h"
-#include <QtCore/QByteArray>
-#include <QtCore/QCoreApplication>
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QString>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qstring.h>
#include <libxslt/xsltutils.h>
#include <libxslt/transform.h>
#ifndef XMLUTILS_LIBXSLT_H
#define XMLUTILS_LIBXSLT_H
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
#include <memory>
#ifndef XMLUTILS_QT_H
#define XMLUTILS_QT_H
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
#include <memory>
endmacro()
macro(shiboken_validate_python_version)
- if(Python_VERSION_MAJOR EQUAL "3" AND Python_VERSION_MINOR LESS "8")
+ if(Python_VERSION_MAJOR EQUAL "3" AND Python_VERSION_MINOR LESS "9")
message(FATAL_ERROR
- "Shiboken requires Python 3.8+.")
+ "Shiboken requires Python 3.9+.")
endif()
endmacro()
.. grid-item-card::
:class-item: text-center
- Reference and functionallities.
+ Reference and functionality.
+++
.. button-ref:: typesystem
:color: primary
from argparse import ArgumentParser, RawTextHelpFormatter
-DESC="""Qhp file updater
+DESC = """Qhp file updater
Replaces virtual folder ids in .qhp files preparing for
registering the documentation in Qt Assistant."""
help='String to be injected into the Qhp file.')
arg_parser.add_argument("--pyside", "-p", action="store_true",
help="Strip the PySide module path off the index entries.")
- arg_parser.add_argument("file", type=str, help='Qhp filename.')
+ arg_parser.add_argument("file", type=str, help='Qhp filename.')
options = arg_parser.parse_args()
virtual_folder = options.vfolder
strip_pyside_module = options.pyside
:maxdepth: 1
Function argument modifications <typesystem_arguments.rst>
+ typesystem_overloads.rst
typesystem_codeinjection.rst
typesystem_converters.rst
typesystem_containers.rst
:maxdepth: 1
typesystem_ownership.rst
+ typesystem_objectvalue.rst
Extra options and Python caveats
--------------------------------
Function argument modifications consist of a list of ``modify-argument`` nodes
contained in :ref:`modify-function`, :ref:`add-function` or
:ref:`declare-function` nodes. Nested :ref:`remove-argument`,
-:ref:`replace-default-expression`, :ref:`remove-default-expression`,
-:ref:`replace-type`, :ref:`reference-count` and :ref:`define-ownership`
-nodes specify the details of the modification.
+:ref:`rename-to`, :ref:`remove-default-expression`, :ref:`replace-default-expression`,
+:ref:`replace-type`, :ref:`define-ownership`, :ref:`parent-on-arguments`,
+:ref:`reference-count`, :ref:`conversionrule-on-arguments` and
+and :ref:`replace-value` nodes specify the details of the modification.
.. code-block:: xml
Remember that our hypothetical view cannot become a :ref:`parent` of the
model, since the said model could be used by other views as well.
-.. _parent:
+.. _parent-on-arguments:
parent
^^^^^^
</modify-argument>
The ``class`` attribute accepts one of the following values to define the
-conversion direction to be either ``target-to-native`` or ``native-to-target``:
+conversion direction to be either ``target-to-native`` (Python to C++)
+or ``native-to-target`` (C++ to Python):
* ``native``: Defines the conversion direction to be ``target-to-native``.
It is similar to the existing ``<target-to-native>`` element.
It is similar to the existing ``<native-to-target>`` element.
See :ref:`Conversion Rule Tag <conversion-rule-tag>` for more information.
+The conversions appear in the generated code (see :ref:`codegenerationterminology`)
+as follows:
+
++--------------+----------------+-------------+
+| | Python Wrapper | C++ Wrapper |
++--------------+----------------+-------------+
+|Parameters | ``native`` | ``target`` |
++--------------+----------------+-------------+
+|Return value | ``target`` | ``native`` |
++--------------+----------------+-------------+
+
This node is typically used in combination with the :ref:`replace-type` and
:ref:`remove-argument` nodes. The given code is used instead of the generator's
conversion code.
+An example for removing an argument might be a C++ function accepting a C-style
+array and a length parameter, which is modified to receive a Python list
+instead. The array parameter's type would be modified to ``PySequence`` with
+``native`` conversion rule; and the length parameter would be removed,
+specifying a native ``native`` conversion rule which would determine the value
+from the size of the Python sequence passed.
+
Writing %N in the code (where N is a number), will insert the name of the
nth argument. Alternatively, %in and %out which will be replaced with the
name of the conversion's input and output variable, respectively. Note the
bool %out = (bool) %in;
</conversion-rule>
+.. note::
+ Conversion rules for the `C++ Wrapper` only need to be specified if the
+ function is virtual and overidable.
+
.. note::
You can also use the ``conversion-rule`` node to specify
.. code-block:: xml
<inject-code class="target" position="beginning | end">
- %CPPSELF.originalMethodName();
+ %CPPSELF.%FUNCTION_NAME();
</inject-code>
.. code-block:: xml
<inject-code class="target" position="beginning | end">
- %CPPSELF.%FUNCTION_NAME();
+ %CPPSELF.originalMethodName();
</inject-code>
// C++ class
struct Complex {
- Complex(double real, double imag);
+ explicit Complex(double real, double imag);
+
double real() const;
double imag() const;
};
<!-- Code injection at module level. -->
<inject-code class="native" position="beginning">
- static bool Check2TupleOfNumbers(PyObject* pyIn) {
- if (!PySequence_Check(pyIn) || !(PySequence_Size(pyIn) == 2))
+ static bool Check2TupleOfNumbers(PyObject *pyIn)
+ {
+ if (PySequence_Check(pyIn) == 0 || PySequence_Size(pyIn) != 2)
return false;
Shiboken::AutoDecRef pyReal(PySequence_GetItem(pyIn, 0));
if (!PyNumber_Check(pyReal))
Converters for :ref:`container-type <container-type>` are pretty much the same as for other type,
except that they make use of the type system variables
-:ref:`%INTYPE_# <intype_n>` and :ref:`%OUTTYPE_# <outtype_n>`.
+:ref:`%INTYPE_# <intype_n>` and :ref:`%OUTTYPE_# <outtype_n>` denoting the
+template parameters.
|project| combines the conversion code for containers with the conversion
defined (or automatically generated) for the containers.
<native-to-target>
PyObject* %out = PyDict_New();
- %INTYPE::const_iterator it = %in.begin();
- for (; it != %in.end(); ++it) {
- %INTYPE_0 key = it->first;
- %INTYPE_1 value = it->second;
- PyDict_SetItem(%out,
+ for (auto it = %in.cbegin(), end = %in.cend(); it != end; ++it) {
+ const auto &key = it->first;
+ const auto &value = it->second;
+ PyDict_SetItem(%out,
%CONVERTTOPYTHON[%INTYPE_0](key),
- %CONVERTTOPYTHON[%INTYPE_1](value));
+ %CONVERTTOPYTHON[%INTYPE_1](value));
}
return %out;
</native-to-target>
<target-to-native>
<add-conversion type="PyDict">
- PyObject* key;
- PyObject* value;
+ PyObject *key{};
+ PyObject *value{};
Py_ssize_t pos = 0;
while (PyDict_Next(%in, &pos, &key, &value)) {
%OUTTYPE_0 cppKey = %CONVERTTOCPP[%OUTTYPE_0](key);
For this case, a number of pre-defined conversion templates
are provided (see :ref:`predefined_templates`).
-.. _variables_and_functions:
+.. _converter_variables_and_functions:
-Variables & Functions
-=====================
+Converter Variables & Functions
+===============================
.. _in:
.. _intype_n:
**%INTYPE_#**
- Replaced by the name of the #th type used in a container.
+ Replaced by the name of the #th template parameter type used in a container.
.. _outtype:
.. _outtype_n:
**%OUTTYPE_#**
- Replaced by the name of the #th type used in a container.
+ Replaced by the name of the #th template parameter type used in a container.
.. _checktype:
<value-type>
<inject-documentation mode="append | prepend | replace" format="native | target"
emphasis="none | language-note"
+ target = "documentation | docstring"
file="[file]" snippet="[label]">
// the documentation
</inject-code>
The optional ``snippet`` attribute specifies the snippet label
(see :ref:`external-snippets`).
+The optional ``target`` attribute can be used to specify that the documentation snippet
+is meant to be used as a Python doc string during code generation instead of documentation.
+``mode="replace"`` must be used for this.
+
At the moment the only supported backend is Sphinx.
If the injected documentation contains a Sphinx function directive, no
virtual int getInt() const;
-For the binding itself, use the common argument modifications (removing
-arguments, modifying return types with injected code snippets) to modify the
-signature.
+In most cases, this can be achieved by specifying conversion rules for
+arguments (see :ref:`conversionrule-on-arguments`, :ref:`modify-argument`)
+for the Python wrapper as well as C++ wrapper code.
-To make it possible to reimplement the function in Python with the modified
-signature, add a ``python-override`` function with that signature, using an
-arbitrary name for disambiguation:
+For cases where this is not possible, code implementing a Python override can
+be generated for arbitrary signatures.
+
+Taking the above example, to make it possible to reimplement the function in
+Python with the modified signature, add a ``python-override`` function with
+that signature, using an arbitrary name for disambiguation:
.. code-block:: xml
--- /dev/null
+.. _objects-values:
+
+******************
+Objects and Values
+******************
+
+Class exposed by shiboken may be specified as either **object types**
+indicated by the :ref:`object-type` XML element, or **value types** indicated
+by :ref:`value-type` XML element.
+
+The choice should follow the usage of the class in C++.
+
+Non-copyable classes with virtual functions that form an inheritance hierarchy
+should be **object types**. They are typically passed by pointer in C++ and
+stored on the heap.
+
+Classes with value semantics should be **value types**. They are typically
+passed by value in C++, and stored on the function call stack. This is also a
+good default for the remaining classes.
+
+When passing **value types** to functions, shiboken will generate code to
+handle implicit conversions as is done in C++. For example, in Qt, it is
+possible to pass instances of **QColor** to a function taking a **QBrush**,
+since **QBrush** has a constructor accepting **QColor**.
+
+Aspects of Value Types
+======================
+
+shiboken tries to detect whether a class is default/copy constructible when
+parsing the class declaration. No detection is done to for movability; it is
+assumed to be enabled by default. These values can be overridden by attributes
+on the :ref:`value-type` XML element in case auto-detection fails. For
+non-default constructible types, it is possible to specify a default
+constructor expression to work around.
+
+shiboken assumes that copy constructible classes also have a copy assignment
+operator.
+
+Default constructibility
+------------------------
+
+Value types should be default constructible.
+
+For generating virtual functions returning value types by value, this
+is a hard requirement.
+
+For generating bindings (passing arguments), non-default constructible types
+will use pointer conversions instead of copy conversion. Implicit conversions
+will then not be generated. Otherwise, it should be equivalent for passing by
+value or const reference. When passing a non-default constructible type by
+reference to a function that actually modifies the parameter, the modification
+will affect the passed instance.
+
+Copy constructibility
+---------------------
+
+Copy constructibility/copy assignment is required by the value converters
+generated by shiboken.
+
+For move-only types, shiboken will then insert ``std::move()`` where needed.
+This will leave moved-out instances behind.
--- /dev/null
+.. _overload-removal:
+
+Restricting Function Overloads
+------------------------------
+
+Some class member functions have a number of overloads that differ in one parameter:
+
+.. code-block:: c++
+
+ class QByteArray {
+ public:
+ ...
+ static QByteArray number(int, int base = 10);
+ static QByteArray number(unsigned int, int base = 10);
+ static QByteArray number(long, int base = 10);
+ static QByteArray number(unsigned long, int base = 10);
+ static QByteArray number(long long, int base = 10);
+ static QByteArray number(unsigned long long, int base = 10);
+ ...
+
+In this case, it does not make sense to generate a binding for ``QByteArray number(int,...)``
+since it is equivalent to ``QByteArray number(long long,...)``.
+
+In the type system file, it is possible to specify a rule stating that the ``int``
+overload is to be removed when an ``long long`` overload exists by using
+the ``<overload-removal>`` element:
+
+.. code-block:: xml
+
+ <overload-removal type="long long" replaces="int"/>
+
+The ``type`` attribute specifies the preferred type and the
+``replaces`` attribute specifies a ';'-delimited list of types to be removed.
+
+.. note:: This is limited to the first 4 arguments of types that are passed by value or const-ref.
+
+.. note:: The rules are applied in the order specified. That is, a rule specifying that ``int``
+ replaces ``short`` should go before a rule rule specifying that ``long long`` replaces ``int``.
-.. _special-functions:
+.. _special-python-functions:
-Special functions
------------------
+Special Python functions
+------------------------
.. _sequence-protocol:
It may contain :ref:`add-function`, :ref:`container-type`,
:ref:`custom-type`, :ref:`enum-type`, :ref:`extra-includes`, :ref:`function`,
:ref:`load-typesystem`, :ref:`namespace`, :ref:`object-type`,
-:ref:`opaque-container`,
+:ref:`opaque-container`, :ref:`overload-removal`,
:ref:`primitive-type`, :ref:`rejection`, :ref:`smart-pointer-type`,
:ref:`suppress-warning`, :ref:`template`, :ref:`system_include`,
:ref:`typedef-type` or :ref:`value-type` child nodes.
value-type
^^^^^^^^^^
-The ``value-type`` node indicates that the given C++ type is mapped onto the target
-language as a value type. This means that it is an object passed by value on C++,
-i.e. it is stored in the function call stack. It is a child of the :ref:`typesystem_details`
+The ``value-type`` node indicates that the given C++ type is mapped onto the
+target language as a value type (see :ref:`objects-values`). This means that it
+is an object passed by value on C++, i.e. it is stored in the function call
+stack.
+
+It is a child of the :ref:`typesystem_details`
node or other type nodes and may contain :ref:`add-function`, :ref:`add-pymethoddef`,
:ref:`configuration-element`, :ref:`declare-function`, :ref:`conversion-rule`,
:ref:`enum-type`, :ref:`extra-includes`, :ref:`include-element`, :ref:`modify-function`,
<typesystem>
<value-type name="..." since="..."
+ default-constructible="yes | no"
copyable="yes | no"
+ movable="yes | no"
allow-thread="..."
disable-wrapper="yes | no"
exception-handling="..."
</typesystem>
The **name** attribute is the fully qualified C++ class name, such as
-"QMatrix" or "QPainterPath::Element". The **copyable** attribute is used to
-force or not specify if this type is copyable. The *optional* **hash-function**
+"QMatrix" or "QPainterPath::Element". The *optional* **hash-function**
attribute informs the function name of a hash function for the type.
-The *optional* attribute **stream** specifies whether this type will be able to
-use externally defined operators, like QDataStream << and >>. If equals to **yes**,
-these operators will be called as normal methods within the current class.
-
-The *optional* **since** value is used to specify the API version of this type.
+The *optional* **default-constructible** attribute is used to override the
+built-in detection of the default constructor.
The *optional* **default-constructor** specifies the minimal constructor
call to build one instance of the value-type. This is not needed when the
on its constructor signatures, thus **default-constructor** is used only in
very odd cases.
+The *optional* **copyable** attribute is used to override the built-in
+detection of the copy constructor. This also implies the type has an assignment
+operator; which is important for the copy conversion.
+
+The *optional* **movable** attribute is used to specify whether the type has a
+move constructor. The default is "yes"; there is no auto-detection.
+
+The *optional* attribute **stream** specifies whether this type will be able to
+use externally defined operators, like QDataStream << and >>. If equals to **yes**,
+these operators will be called as normal methods within the current class.
+
+The *optional* **since** value is used to specify the API version of this type.
+
For the *optional* **disable-wrapper** and **generate-functions**
attributes, see :ref:`object-type`.
object-type
^^^^^^^^^^^
-The object-type node indicates that the given C++ type is mapped onto the target
-language as an object type. This means that it is an object passed by pointer on
-C++ and it is stored on the heap. It is a child of the :ref:`typesystem_details` node
-or other type nodes and may contain :ref:`add-function`, :ref:`add-pymethoddef`,
+The object-type node indicates that the given C++ type is mapped onto the
+target language as an object type (see :ref:`objects-values`). This means that
+it is an object passed by pointer on C++ and it is stored on the heap.
+
+It is a child of the :ref:`typesystem_details` node or other type nodes and may
+contain :ref:`add-function`, :ref:`add-pymethoddef`,
:ref:`configuration-element`, :ref:`declare-function`, :ref:`enum-type`,
:ref:`extra-includes`, :ref:`include-element`, :ref:`modify-function`,
``object-type``, :ref:`smart-pointer-type`, :ref:`typedef-type` or
doc-file = "..." />
</typesystem>
-The **name** attribute is the fully qualified C++ class name. If there is no
-C++ base class, the default-superclass attribute can be used to specify a
-superclass for the given type, in the generated target language API. The
+The **name** attribute is the fully qualified C++ class name. The
**copyable** and **hash-function** attributes are the same as described for
:ref:`value-type`.
+The *optional* **default-superclass** attribute can be used to specify a
+superclass for the given type in the generated target language API.
+This can be useful if the C++ base class is not exposed. The specified
+super class needs to be a direct base class of the class in question.
+
The *optional* **force-abstract** attribute forces the class to be
abstract, disabling its instantiation. The generator will normally detect
this automatically unless the class inherits from an abstract base class
value-check-method="..."
null-check-method="..."
reset-method="..."
- instantiations="..."/>
+ instantiations="..."
+ excluded-instantiations="..."/>
</typesystem>
The *optional* attribute **reset-method** specifies a method
that can be used to clear the pointer.
-The *optional* instantiations attribute specifies a comma-separated
+The *optional* **instantiations** attribute specifies a comma-separated
list of instantiation types. When left empty, all instantiations
found in the code will be generated. The type name might optionally
be followed an equal sign and the Python type name, for example
for example, ``std`` for ``std::shared_ptr``. Preceding
the type name by ``::`` causes it to be in the global namespace.
+The *optional* **excluded-instantiations** attribute specifies a
+comma-separated list of types to be excluded from instantiating. Typically,
+this is used to exclude instantiations present in an underlying base module to
+prevent symbol clashes.
+
The *optional* attribute **type** specifies the type:
*shared*
Other keywords can be specified using the
:ref:`--keywords <conditional_keywords>` command line option.
-.. _private_types:
-
Defining Entities
^^^^^^^^^^^^^^^^^
This allows for defining function signatures depending on platform
in conjunction with :ref:`conditional_processing`.
+.. _private_types:
+
Private Types
^^^^^^^^^^^^^
.. code-block:: xml
<typesystem>
- <template name="my_template">
+ <template name="my_template" file="[file]" snippet="[label]"/>
// the code
</template>
</typesystem>
+Code may either be specified as element text (note that entities need to be
+used for special characters like '<'), or fetched from an external code snippet
+by specifying the ``file`` and ``snippet`` attributes (see
+:ref:`external-snippets`).
+
Use the ``insert-template`` node to insert the template code (identified
by the template's ``name`` attribute) into the generated code base.
Variables
=========
+In addition to the below listed variables, there are some variables specific to type
+conversion code (see :ref:`converter_variables_and_functions`).
.. _cpp_return_argument:
shiboken/cppgenerator_container.cpp
shiboken/cppgenerator_smartpointer.cpp
shiboken/ctypenames.h
+shiboken/overridecacheentry.h shiboken/overridecacheentry.cpp
shiboken/generatorargument.cpp shiboken/generatorargument.h shiboken/generatorstrings.h
shiboken/headergenerator.cpp shiboken/headergenerator.h
shiboken/overloaddata.cpp shiboken/overloaddata.h
#include "qtcompat.h"
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
using namespace Qt::StringLiterals;
#ifndef DEFAULTVALUE_H
#define DEFAULTVALUE_H
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
QT_FORWARD_DECLARE_CLASS(QDebug);
#include "qtcompat.h"
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QFileInfo>
-#include <QtCore/QRegularExpression>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qregularexpression.h>
using namespace Qt::StringLiterals;
return std::make_shared<GeneratorOptionsParser>(&GeneratorPrivate::m_options);
}
+QString Generator::fileNameForClassHelper(const AbstractMetaClassCPtr &metaClass,
+ const QString &suffix,
+ FileNameFlags flags)
+{
+ QString fileNameBase = flags.testFlag(FileNameFlag::UnqualifiedName)
+ ? metaClass->name() : metaClass->qualifiedCppName();
+ if (!flags.testFlag(FileNameFlag::KeepCase))
+ fileNameBase = fileNameBase.toLower();
+ fileNameBase.replace(u"::"_s, u"_"_s);
+ return fileNameBase + suffix;
+}
+
QString Generator::fileNameForContextHelper(const GeneratorContext &context,
const QString &suffix,
FileNameFlags flags)
{
- if (!context.forSmartPointer()) {
- const auto metaClass = context.metaClass();
- QString fileNameBase = flags.testFlag(FileNameFlag::UnqualifiedName)
- ? metaClass->name() : metaClass->qualifiedCppName();
- if (!flags.testFlag(FileNameFlag::KeepCase))
- fileNameBase = fileNameBase.toLower();
- fileNameBase.replace(u"::"_s, u"_"_s);
- return fileNameBase + suffix;
- }
+ if (!context.forSmartPointer())
+ return fileNameForClassHelper(context.metaClass(), suffix, flags);
- // FIXME: PYSIDE7: Use the above code path for all types. Note the file
+ // FIXME: PYSIDE7: Use the above code path for all types and change
+ // parameter type to AbstractMetaClassCPtr. Note the file
// names will then change to reflect the namespaces of the pointee
// (smart/integer2).
const AbstractMetaType &smartPointerType = context.preciseType();
}
template<typename T>
-static QString getClassTargetFullName_(T t, bool includePackageName)
+static QString getClassTargetFullName_(const T &t, bool includePackageName)
{
QString name = t->name();
AbstractMetaClassCPtr context = t->enclosingClass();
#include <abstractmetalang_typedefs.h>
#include <typedatabase_typedefs.h>
-#include <QtCore/QList>
+#include <QtCore/qlist.h>
#include <memory>
#include <optional>
SkipRemovedArguments = 0x00000100,
SkipDefaultValues = 0x00000200,
+ // Implementation of call to Python override
+ PythonOverrideImplementation = 0x0400,
};
Q_DECLARE_FLAGS(Options, Option)
static QString fileNameForContextHelper(const GeneratorContext &context,
const QString &suffix,
FileNameFlags flags = {});
+ static QString fileNameForClassHelper(const AbstractMetaClassCPtr &metaClass,
+ const QString &suffix,
+ FileNameFlags flags = {});
/// Returns all primitive types found by APIExtractor
static PrimitiveTypeEntryCList primitiveTypes();
#include "generatorcontext.h"
#include <abstractmetalang.h>
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
using namespace Qt::StringLiterals;
#include <abstractmetalang_typedefs.h>
#include <abstractmetatype.h>
-#include <QtCore/QList>
+#include <QtCore/qlist.h>
QT_FORWARD_DECLARE_CLASS(QDebug);
#include <reporthandler.h>
#include <typedatabase.h>
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QLibrary>
-#include <QtCore/QVariant>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qlibrary.h>
+#include <QtCore/qscopeguard.h>
+#include <QtCore/qvariant.h>
#include "qtcompat.h"
QHashSeed::setDeterministicGlobalSeed();
ReportHandler::install();
- if (ReportHandler::isDebug(ReportHandler::SparseDebug))
- qCInfo(lcShiboken()).noquote().nospace() << appName << ' ' << argV.join(u' ');
+ ReportHandler::addGeneralMessage(msgCommandLineArguments(argV));
Options options;
options.setOptions(argV);
return EXIT_FAILURE;
}
+ auto logWriterFunc = [&commonOptions]() {
+ ReportHandler::writeGeneralLogFile(commonOptions.outputDirectory);
+ };
+ auto logWriter = qScopeGuard(logWriterFunc);
+
extractor.setCppFileNames(cppFileNames);
extractor.setTypeSystem(commonOptions.typeSystemFileName);
ApiExtractorFlags apiExtractorFlags;
- if (generators.constFirst()->usePySideExtensions())
+ if (Generator::usePySideExtensions())
apiExtractorFlags.setFlag(ApiExtractorFlag::UsePySideExtensions);
- if (generators.constFirst()->avoidProtectedHack())
+ if (Generator::avoidProtectedHack())
apiExtractorFlags.setFlag(ApiExtractorFlag::AvoidProtectedHack);
const std::optional<ApiExtractorResult> apiOpt = extractor.run(apiExtractorFlags);
#include "qtcompat.h"
-#include <QtCore/QTextStream>
-#include <QtCore/QFile>
-#include <QtCore/QDir>
-#include <QtCore/QJsonArray>
-#include <QtCore/QJsonDocument>
-#include <QtCore/QJsonObject>
-#include <QtCore/QOperatingSystemVersion>
-#include <QtCore/QSet>
+#include <QtCore/qtextstream.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qjsonarray.h>
+#include <QtCore/qjsondocument.h>
+#include <QtCore/qjsonobject.h>
+#include <QtCore/qoperatingsystemversion.h>
+#include <QtCore/qset.h>
#include <algorithm>
#include <limits>
QString name;
QString fullName;
QString file;
+ bool nested = false;
};
static bool classEntryLessThan(const DocClassEntry &e1, const DocClassEntry &e2)
auto dot = name.lastIndexOf(u'.');
if (dot != -1)
name.remove(0, dot + 1);
- docPackage->classPages.append({name, fullClassName, newFileName});
+ docPackage->classPages.append({name, fullClassName, newFileName, false});
}
extraTocEntries->append(fileNameToTocEntry(newFileName));
}
qCDebug(lcShibokenDoc, "Generating Documentation for %s", qPrintable(metaClass->fullName()));
+ const bool nested = metaClass->enclosingClass() != nullptr;
m_packages[metaClass->package()].classPages.append({metaClass->name(), metaClass->fullName(),
- fileNameForContext(classContext)});
+ fileNameForContext(classContext), nested});
doGenerateClass(s, targetDir, metaClass);
{
bool didSomething = false;
for (const DocModification &mod : mods) {
- if (mod.mode() == mode) {
+ if (mod.target() == DocumentationTarget::Documentation && mod.mode() == mode) {
writeFormattedText(s, mod, scope, images);
didSomething = true;
}
return name;
if (type.typeUsagePattern() == AbstractMetaType::PrimitivePattern) {
- const auto &basicName = basicReferencedTypeEntry(type.typeEntry())->name();
+ const auto &basicName = type.basicPrimitiveName();
if (AbstractMetaType::cppSignedIntTypes().contains(basicName)
|| AbstractMetaType::cppUnsignedIntTypes().contains(basicName)) {
return intT;
QtXmlToSphinxImages parsedImages;
TextStream& s = output.stream;
+ TypeSystemTypeEntryCPtr typeSystemEntry = typeDb->findTypeSystemType(it.key());
+ Q_ASSERT(typeSystemEntry);
+ const auto docMode = typeSystemEntry->docMode();
+
const QString &title = it.key();
s << ".. module:: " << title << "\n\n" << headline(title, '*');
<< ":maxdepth: 1\n\n";
if (hasGlobals)
s << globalsPage << '\n';
- for (const auto &e : std::as_const(docPackage.classPages))
- s << e.file << '\n';
+ for (const auto &e : std::as_const(docPackage.classPages)) {
+ if (!e.nested || docMode == TypeSystem::DocMode::Flat)
+ s << e.file << '\n';
+ }
s << "\n\n" << outdent << outdent << headline("Detailed Description");
// module doc is always wrong and C++istic, so go straight to the extra directory!
<< moduleDocumentation.qmlTypesUrl << ">`_\n\n";
}
- TypeSystemTypeEntryCPtr typeSystemEntry = typeDb->findTypeSystemType(it.key());
- Q_ASSERT(typeSystemEntry);
- writeFancyToc(s, "List of Classes", classEntryListToToc(docPackage.classPages,
- typeSystemEntry->docMode()),
+ writeFancyToc(s, "List of Classes", classEntryListToToc(docPackage.classPages, docMode),
"class"_L1);
writeFancyToc(s, "List of Decorators", fileListToToc(docPackage.decoratorPages),
"deco"_L1);
ResolvedDocImage
QtDocGenerator::resolveImage(const QtXmlToSphinxImage &image,
const QStringList &sourceDirs,
- const QString &targetDir) const
+ const QString &targetDir)
{
QString hrefBase;
QString hrefName = image.href; // split "images/a.png"
// Copy parsed images from WebXML to doc/base
void QtDocGenerator::copyParsedImages(const QtXmlToSphinxImages &images,
const QStringList &sourceDocumentFiles,
- const QString &targetDocumentDir) const
+ const QString &targetDocumentDir)
{
if (images.isEmpty())
return;
#ifndef DOCGENERATOR_H
#define DOCGENERATOR_H
-#include <QtCore/QStringList>
-#include <QtCore/QMap>
-#include <QtCore/QScopedPointer>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qmap.h>
+#include <QtCore/qscopedpointer.h>
#include "generator.h"
#include "documentation.h"
const DocPackage &docPackage);
void writeAdditionalDocumentation() const;
bool writeInheritanceFile();
- ResolvedDocImage resolveImage(const QtXmlToSphinxImage &image,
- const QStringList &sourceDirs,
- const QString &targetDir) const;
- void copyParsedImages(const QtXmlToSphinxImages &images,
- const QStringList &sourceDocumentFiles,
- const QString &targetDocumentFile) const;
+ static ResolvedDocImage resolveImage(const QtXmlToSphinxImage &image,
+ const QStringList &sourceDirs,
+ const QString &targetDir);
+ static void copyParsedImages(const QtXmlToSphinxImages &images,
+ const QStringList &sourceDocumentFiles,
+ const QString &targetDocumentFile);
QString translateToPythonType(const AbstractMetaType &type,
const AbstractMetaClassCPtr &cppClass,
bool createRef = true) const;
#include "qtxmltosphinx.h"
#include "exception.h"
+#include <filecache.h>
#include "qtxmltosphinxinterface.h"
#include <codesniphelpers.h>
#include "rstformat.h"
#include "qtcompat.h"
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QFileInfo>
-#include <QtCore/QHash>
-#include <QtCore/QLoggingCategory>
-#include <QtCore/QRegularExpression>
-#include <QtCore/QXmlStreamReader>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qxmlstream.h>
using namespace Qt::StringLiterals;
if (QFileInfo::exists(location))
return location;
}
- return QString();
+ return {};
}
enum class SnippetType
// use existing fallback paths first.
const auto type = snippetType(path);
if (type == SnippetType::Other && !fallbackPath.isEmpty()) {
- const QString code = readFromLocation(fallbackPath, identifier, errorMessage);
+ const QString code = readSnippet(fallbackPath, identifier, errorMessage);
return {code, code.isNull() ? Snippet::Error : Snippet::Fallback};
}
if (!rewrittenPath.isEmpty()) {
rewrittenPath.replace(m_parameters.codeSnippetRewriteOld,
m_parameters.codeSnippetRewriteNew);
- const QString code = readFromLocation(rewrittenPath, identifier, errorMessage);
+ const QString code = readSnippet(rewrittenPath, identifier, errorMessage);
m_containsAutoTranslations = true;
return {code, code.isNull() ? Snippet::Error : Snippet::Converted};
}
resolvedPath = resolveFile(locations, pySnippetName(path, type));
if (!resolvedPath.isEmpty()) {
- const QString code = readFromLocation(resolvedPath, identifier, errorMessage);
+ const QString code = readSnippet(resolvedPath, identifier, errorMessage);
return {code, code.isNull() ? Snippet::Error : Snippet::Converted};
}
}
resolvedPath = resolveFile(locations, path);
if (!resolvedPath.isEmpty()) {
- const QString code = readFromLocation(resolvedPath, identifier, errorMessage);
+ const QString code = readSnippet(resolvedPath, identifier, errorMessage);
return {code, code.isNull() ? Snippet::Error : Snippet::Resolved};
}
if (!fallbackPath.isEmpty()) {
*errorMessage = msgFallbackWarning(path, identifier, fallbackPath);
- const QString code = readFromLocation(fallbackPath, identifier, errorMessage);
+ const QString code = readSnippet(fallbackPath, identifier, errorMessage);
return {code, code.isNull() ? Snippet::Error : Snippet::Fallback};
}
return {{}, Snippet::Error};
}
-static QString msgSnippetNotFound(const QIODevice &inputFile,
- const QString &identifier)
-{
- return u"Code snippet file found ("_s + fileNameOfDevice(&inputFile)
- + u"), but snippet ["_s + identifier + u"] not found."_s;
-}
-
-static QString msgEmptySnippet(const QIODevice &inputFile, int lineNo,
+static QString msgEmptySnippet(const QString &inputFile,
const QString &identifier)
{
- return u"Empty code snippet ["_s + identifier + u"] at "_s
- + fileNameOfDevice(&inputFile) + u':' + QString::number(lineNo);
+ return "Empty code snippet ["_L1 + identifier + "] in "_L1
+ + QDir::toNativeSeparators(inputFile);
}
-// Pattern to match qdoc snippet IDs with "#/// [id]" comments and helper to find ID
-static const QRegularExpression &snippetIdPattern()
+// Pattern to match qdoc snippet IDs "#/// [id]" or "# ![id1] # ![id2]"
+static QRegularExpression snippetIdPattern(const QString &snippetId)
{
- static const QRegularExpression result(uR"RX((//|#) *! *\[([^]]+)\])RX"_s);
+ QString pattern = "(//|#) *! *\\["_L1
+ + QRegularExpression::escape(snippetId) + "\\]"_L1;
+ QRegularExpression result(pattern);
Q_ASSERT(result.isValid());
return result;
}
-static bool matchesSnippetId(QRegularExpressionMatchIterator it,
- const QString &identifier)
-{
- while (it.hasNext()) {
- if (it.next().captured(2) == identifier)
- return true;
- }
- return false;
-}
-
-QString QtXmlToSphinx::readSnippet(QIODevice &inputFile, const QString &identifier,
+QString QtXmlToSphinx::readSnippet(const QString &location, const QString &identifier,
QString *errorMessage)
{
- const QByteArray identifierBA = identifier.toUtf8();
- // Lambda that matches the snippet id
- const auto snippetIdPred = [&identifierBA, &identifier](const QByteArray &lineBA)
- {
- const bool isComment = lineBA.contains('/') || lineBA.contains('#');
- if (!isComment || !lineBA.contains(identifierBA))
- return false;
- const QString line = QString::fromUtf8(lineBA);
- return matchesSnippetId(snippetIdPattern().globalMatch(line), identifier);
- };
+ static FileCache cache;
- // Find beginning, skip over
- int lineNo = 1;
- for (; !inputFile.atEnd() && !snippetIdPred(inputFile.readLine());
- ++lineNo) {
- }
-
- if (inputFile.atEnd()) {
- *errorMessage = msgSnippetNotFound(inputFile, identifier);
+ const auto result = identifier.isEmpty() ? cache.fileContents(location)
+ : cache.fileSnippet(location, identifier, snippetIdPattern(identifier));
+ if (!result.has_value()) {
+ *errorMessage = cache.errorString();
return {};
}
- QString code;
- for (; !inputFile.atEnd(); ++lineNo) {
- const QString line = QString::fromUtf8(inputFile.readLine());
- auto it = snippetIdPattern().globalMatch(line);
- if (it.hasNext()) { // Skip snippet id lines
- if (matchesSnippetId(it, identifier))
- break;
- } else {
- code += line;
- }
- }
-
- if (code.isEmpty())
- *errorMessage = msgEmptySnippet(inputFile, lineNo, identifier);
-
- return code;
-}
-
-QString QtXmlToSphinx::readFromLocation(const QString &location, const QString &identifier,
- QString *errorMessage)
-{
- QFile inputFile;
- inputFile.setFileName(location);
- if (!inputFile.open(QIODevice::ReadOnly)) {
- QTextStream(errorMessage) << "Could not read code snippet file: "
- << QDir::toNativeSeparators(inputFile.fileName())
- << ": " << inputFile.errorString();
- return {}; // null
- }
-
- QString code = u""_s; // non-null
- if (identifier.isEmpty()) {
- while (!inputFile.atEnd())
- code += QString::fromUtf8(inputFile.readLine());
- return CodeSnipHelpers::fixSpaces(code);
- }
+ if (result.value().isEmpty())
+ *errorMessage = msgEmptySnippet(location, identifier);
- code = readSnippet(inputFile, identifier, errorMessage);
- return code.isEmpty() ? QString{} : CodeSnipHelpers::fixSpaces(code); // maintain isNull()
+ return CodeSnipHelpers::fixSpaces(result.value());
}
void QtXmlToSphinx::handleHeadingTag(QXmlStreamReader& reader)
if (m_parameters.snippetComparison && snippet.result == Snippet::Converted
&& !fallbackPath.isEmpty()) {
- const QString fallbackCode = readFromLocation(fallbackPath, identifier, &errorMessage);
+ const QString fallbackCode = readSnippet(fallbackPath, identifier, &errorMessage);
debug(msgSnippetComparison(location, identifier, snippet.code, fallbackCode));
}
QString location = reader.text().toString();
location.prepend(m_parameters.libSourceDir + u'/');
QString errorMessage;
- QString code = readFromLocation(location, QString(), &errorMessage);
+ QString code = readSnippet(location, QString(), &errorMessage);
if (!errorMessage.isEmpty())
warn(msgTagWarning(reader, m_context, m_lastTagName, errorMessage));
m_output << "::\n\n";
void QtXmlToSphinx::stripPythonQualifiers(QString *s)
{
- const int lastSep = s->lastIndexOf(u'.');
+ const auto lastSep = s->lastIndexOf(u'.');
if (lastSep != -1)
s->remove(0, lastSep + 1);
}
#include <textstream.h>
-#include <QtCore/QList>
-#include <QtCore/QScopedPointer>
-#include <QtCore/QStack>
+#include <QtCore/qlist.h>
+#include <QtCore/qscopedpointer.h>
+#include <QtCore/qstack.h>
#include <memory>
static void stripPythonQualifiers(QString *s);
- // For testing
- static QString readSnippet(QIODevice &inputFile, const QString &identifier,
+ static QString readSnippet(const QString &location, const QString &identifier,
QString *errorMessage);
private:
const QString &identifier,
const QString &fallbackPath,
QString *errorMessage);
- static QString readFromLocation(const QString &location, const QString &identifier,
- QString *errorMessage);
void pushOutputBuffer();
QString popOutputBuffer();
void writeTable(Table& table);
#ifndef QTXMLTOSPHINXINTERFACE_H
#define QTXMLTOSPHINXINTERFACE_H
-#include <QtCore/QStringList>
+#include <QtCore/qstringlist.h>
QT_FORWARD_DECLARE_CLASS(QLoggingCategory)
QT_FORWARD_DECLARE_CLASS(QDebug)
#include <textstream.h>
-#include <QtCore/QByteArray>
-#include <QtCore/QString>
-#include <QtCore/QTextStream>
-#include <QtCore/QVersionNumber>
+#include <QtCore/qbytearray.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qtextstream.h>
+#include <QtCore/qversionnumber.h>
struct rstVersionAdded
{
#include "qtcompat.h"
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QMetaObject>
-#include <QtCore/QMetaType>
-#include <QtCore/QRegularExpression>
-#include <QtCore/QTextStream>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qmetatype.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qset.h>
+#include <QtCore/qtextstream.h>
#include <algorithm>
#include <cstring>
static bool generateRichComparison(const GeneratorContext &c)
{
- const auto metaClass = c.metaClass();
+ const auto &metaClass = c.metaClass();
if (c.forSmartPointer()) {
auto te = std::static_pointer_cast<const SmartPointerTypeEntry>(metaClass->typeEntry());
return te->smartPointerType() == TypeSystem::SmartPointerType::Shared;
const IncludeGroupList &includes,
const AbstractMetaClassCList &innerClasses) const
{
- const auto metaClass = classContext.metaClass();
+ const auto &metaClass = classContext.metaClass();
// write license comment
s << licenseComment() << '\n';
s << "\n// main header\n" << "#include \""
<< HeaderGenerator::headerFileNameForContext(classContext) << "\"\n";
+ // Wrappers from which overrides are re-used.
+ QSet<QString> wrapperIncludes;
+ for (const auto &f : getReusedOverridenFunctions(metaClass))
+ wrapperIncludes.insert(ShibokenGenerator::headerFileNameForClass(f->ownerClass()));
+ if (!wrapperIncludes.isEmpty()) {
+ s << "\n// Wrappers providing overrides\n";
+ for (const auto &wrapperInclude : std::as_const(wrapperIncludes))
+ s << "#include \"" << wrapperInclude << "\"\n";
+ }
+
if (!innerClasses.isEmpty()) {
s << "\n// inner classes\n";
for (const auto &innerClass : innerClasses) {
}
}
+// Print diagnostics/warnings about special types
+static void warnAboutTypes(const AbstractMetaClassCPtr &metaClass)
+{
+ const auto typeEntry = metaClass->typeEntry();
+ if (typeEntry->isValue()) {
+ // Warn about special value types.
+ if (!typeEntry->isDefaultConstructible()
+ && typeEntry->defaultConstructibleFlag() == TypeSystem::DefaultConstructibleFlag::Unspecified) {
+ ReportHandler::addGeneralMessage("Value type \""_L1 + typeEntry->qualifiedCppName()
+ + "\" is not default constructible."_L1);
+ }
+ if (!typeEntry->isCopyable()
+ && typeEntry->copyableFlag() == TypeSystem::CopyableFlag::Unspecified) {
+ ReportHandler::addGeneralMessage("Value type \""_L1 + typeEntry->qualifiedCppName()
+ + "\" is not copyable, std::move() will be used."_L1);
+ }
+ } else if (typeEntry->isObject())
+ if (metaClass->isDefaultConstructible()
+ && metaClass->isCopyConstructible() && !metaClass->isPolymorphic()
+ && metaClass->baseClass() == nullptr) {
+ qCWarning(lcShiboken, "Object type \"%s\" has default and copy constructors; consider using value-type.",
+ qPrintable(typeEntry->qualifiedCppName()));
+ }
+}
+
/// Function used to write the class generated binding code on the buffer
/// \param s the output buffer
/// \param classContext the pointer to metaclass information
QList<GeneratorContext> *)
{
s.setLanguage(TextStream::Language::Cpp);
- AbstractMetaClassCPtr metaClass = classContext.metaClass();
+ const AbstractMetaClassCPtr &metaClass = classContext.metaClass();
const auto typeEntry = metaClass->typeEntry();
+ warnAboutTypes(metaClass);
+
auto innerClasses = metaClass->innerClasses();
for (auto it = innerClasses.begin(); it != innerClasses.end(); ) {
auto innerTypeEntry = (*it)->typeEntry();
s << outdent << "}\n\n";
}
- int maxOverrides = 0;
+ const auto &wrapperConstructors = ShibokenGenerator::getWrapperConstructors(metaClass);
+
if (useOverrideCaching(classContext.metaClass()))
writeCacheResetNative(s, classContext);
+ for (const auto &func : wrapperConstructors)
+ writeConstructorNative(s, classContext, func);
+ int maxOverrides = 0;
for (const auto &func : metaClass->functions()) {
const auto generation = functionGeneration(func);
- if (generation.testFlag(FunctionGenerationFlag::WrapperConstructor))
- writeConstructorNative(s, classContext, func);
- else if (generation.testFlag(FunctionGenerationFlag::VirtualMethod))
+ if (generation.testFlag(FunctionGenerationFlag::VirtualMethod))
writeVirtualMethodNative(s, func, maxOverrides++);
}
s << openTargetExternC;
+ const auto &constructors = getConstructors(metaClass);
+ if (constructors.isEmpty()) {
+ const char *errorFunc = classContext.metaClass()->isNamespace()
+ ? "setInstantiateNamespace" : "setInstantiateNonConstructible";
+ writeConstructorDummy(s, classContext, errorFunc);
+ } else {
+ OverloadData overloadData(constructors, api());
+ writeConstructorWrapper(s, overloadData, classContext);
+ // On constructors, we also generate the property initializers.
+ writeSignatureInfo(signatureStream, overloadData, true);
+ }
+
const auto &functionGroups = getFunctionGroups(metaClass);
for (auto it = functionGroups.cbegin(), end = functionGroups.cend(); it != end; ++it) {
if (contains(sequenceProtocols(), it.key()) || contains(mappingProtocols(), it.key()))
const auto &rfunc = overloads.constFirst();
OverloadData overloadData(overloads, api());
- if (rfunc->isConstructor()) {
- writeConstructorWrapper(s, overloadData, classContext);
- // On constructors, we also generate the property initializers.
- writeSignatureInfo(signatureStream, overloadData, true);
- }
// call operators
- else if (rfunc->name() == u"operator()") {
+ if (rfunc->name() == u"operator()") {
writeMethodWrapper(s, overloadData, classContext);
writeSignatureInfo(signatureStream, overloadData);
}
void CppGenerator::writeConstructorNative(TextStream &s, const GeneratorContext &classContext,
const AbstractMetaFunctionCPtr &func) const
{
- const QString qualifiedName = classContext.wrapperName() + u"::"_s;
- s << functionSignature(func, qualifiedName, QString(),
+ s << functionSignature(func, classContext.wrapperName(), {},
OriginalTypeDescription | SkipDefaultValues);
if (!func->arguments().isEmpty()) {
s << " : ";
+ typeEntry->qualifiedCppName() + u" >()->tp_name"_s;
}
-// When writing an overridden method of a wrapper class, write the part
-// calling the C++ function in case no overload in Python exists.
void CppGenerator::writeVirtualMethodCppCall(TextStream &s,
const AbstractMetaFunctionCPtr &func,
const QString &funcName,
const CodeSnipList &snips,
const AbstractMetaArgument *lastArg,
const TypeEntryCPtr &retType,
- const QString &returnStatement, bool hasGil) const
+ const QString &returnStatement,
+ bool ownsGil, bool hasGilVar) const
{
if (!snips.isEmpty()) {
writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionBeginning,
}
if (func->isAbstract()) {
- if (!hasGil)
- s << "Shiboken::GilState gil;\n";
+ if (!ownsGil)
+ s << (hasGilVar ? "gil.acquire();\n" : "Shiboken::GilState gil;\n");
s << "Shiboken::Errors::setPureVirtualMethodError(\""
<< func->ownerClass()->name() << '.' << funcName << "\");\n"
<< returnStatement << '\n';
return;
}
- if (hasGil)
+ if (ownsGil)
s << "gil.release();\n";
if (retType)
auto argTypeEntry = type.typeEntry();
// Check for primitive types convertible by Py_BuildValue()
if (argTypeEntry->isPrimitive() && !type.isCString()) {
- const auto pte = basicReferencedTypeEntry(argTypeEntry);
- auto it = formatUnits().constFind(pte->name());
+ auto it = formatUnits().constFind(type.basicPrimitiveName());
if (it != formatUnits().constEnd())
return {arg.name(), it.value()};
}
const QString funcName = func->isOperatorOverload()
? pythonOperatorFunctionName(func) : func->definitionNames().constFirst();
- QString prefix = wrapperName(func->ownerClass()) + u"::"_s;
- s << functionSignature(func, prefix, QString(), Generator::SkipDefaultValues |
- Generator::OriginalTypeDescription)
+ QString className = wrapperName(func->ownerClass());
+ const Options options = Generator::SkipDefaultValues | Generator::OriginalTypeDescription;
+ s << functionSignature(func, className, {}, options)
<< "\n{\n" << indent;
const auto returnStatement = virtualMethodReturn(api(), func,
if (wrapperDiagnostics()) {
s << "std::cerr << ";
#ifndef Q_CC_MSVC // g++ outputs __FUNCTION__ unqualified
- s << '"' << prefix << R"(" << )";
+ s << '"' << className << R"(::" << )";
#endif
s << R"(__FUNCTION__ << ' ' << this << " m_PyMethodCache[" << )"
<< cacheIndex << R"( << "]=" << m_PyMethodCache[)" << cacheIndex
<< R"(] << '\n';)" << '\n';
}
- // PYSIDE-803: Build a boolean cache for unused overrides
- const bool multi_line = func->isVoid() || !snips.isEmpty() || isAbstract;
- s << "if (m_PyMethodCache[" << cacheIndex << "])" << (multi_line ? " {\n" : "\n")
- << indent;
- writeVirtualMethodCppCall(s, func, funcName, snips, lastArg, retType,
- returnStatement.statement, false);
- s << outdent;
- if (multi_line)
- s << "}\n";
-
- s << "Shiboken::GilState gil;\n";
-
- // Get out of virtual method call if someone already threw an error.
- s << "if (" << shibokenErrorsOccurred << ")\n" << indent
- << returnStatement.statement << '\n' << outdent;
-
- s << "static PyObject *nameCache[2] = {};\n";
writeFuncNameVar(s, func, funcName);
- s << "Shiboken::AutoDecRef " << PYTHON_OVERRIDE_VAR
- << "(Shiboken::BindingManager::instance().getOverride(this, nameCache, funcName));\n"
- << "if (" << PYTHON_OVERRIDE_VAR << ".isNull()) {\n" << indent
- << "m_PyMethodCache[" << cacheIndex << "] = true;\n";
+ s << "static PyObject *nameCache[2] = {};\n"
+ << "Shiboken::GilState gil(false);\n"
+ << "Shiboken::AutoDecRef " << PYTHON_OVERRIDE_VAR << "(Sbk_GetPyOverride("
+ << "this, gil, funcName, &m_PyMethodCache[" << cacheIndex << "], nameCache));\n"
+ << "if (pyOverride.isNull()) {\n" << indent;
writeVirtualMethodCppCall(s, func, funcName, snips, lastArg, retType,
- returnStatement.statement, true);
- s << outdent << "}\n\n"; //WS
-
+ returnStatement.statement, false, true);
+ s << outdent << "}\n";
if (!snips.isEmpty()) {
writeCodeSnips(s, snips, TypeSystem::CodeSnipPositionPyOverride,
TypeSystem::ShellCode, func, false, lastArg);
}
+ // Call the (static) Python override implementation
+ if (!func->isVoid())
+ s << "return ";
+
+ auto owner = func->ownerClass();
+ const auto &reusedFuncs = getReusedOverridenFunctions(owner);
+ auto rit = reusedFuncs.constFind(func);
+ const bool canReuse = rit != reusedFuncs.cend();
+ if (canReuse) {
+ s << wrapperName(rit.value()->ownerClass()) << "::"
+ << pythonOverrideImplName(rit.value());
+ } else {
+ s << pythonOverrideImplName(func);
+ }
+ s << "(\"" << owner->name()
+ << "\", funcName, gil, " << PYTHON_OVERRIDE_VAR;
+ for (const auto &arg : func->arguments())
+ s << ", " << (arg.type().useStdMove() ? stdMove(arg.name()) : arg.name());
+ s << ");\n" << outdent << "}\n\n";
+
+ if (canReuse)
+ return;
+ // Write Python override implementation definition
+ s << functionSignature(func, className, {}, options | PythonOverrideImplementation)
+ << "\n{\n" << indent
+ << sbkUnusedVariableCast("ownerClassName") << sbkUnusedVariableCast("funcName")
+ << sbkUnusedVariableCast("gil") << sbkUnusedVariableCast(PYTHON_OVERRIDE_VAR);
+ if (returnStatement.needsReference)
+ writeVirtualMethodStaticReturnVar(s, func);
writeVirtualMethodPythonOverride(s, func, snips, returnStatement);
}
-
void CppGenerator::writeVirtualMethodPythonOverride(TextStream &s,
const AbstractMetaFunctionCPtr &func,
const CodeSnipList &snips,
s << "if (" << PYTHON_RETURN_VAR << ".isNull()) {\n" << indent
<< "// An error happened in python code!\n"
- << "Shiboken::Errors::storePythonOverrideErrorOrPrint(\""
- << func->ownerClass()->name() << "\", funcName);\n"
+ << "Shiboken::Errors::storePythonOverrideErrorOrPrint(ownerClassName, funcName);\n"
<< returnStatement.statement << "\n" << outdent
<< "}\n";
<< cpythonIsConvertibleFunction(func->type())
<< PYTHON_RETURN_VAR << ");\n" << outdent
<< "if (!" << PYTHON_TO_CPP_VAR << ") {\n" << indent
- << "Shiboken::Warnings::warnInvalidReturnValue(\""
- << func->ownerClass()->name() << "\", funcName, "
+ << "Shiboken::Warnings::warnInvalidReturnValue(ownerClassName, funcName, "
<< getVirtualFunctionReturnTypeName(func) << ", "
<< "Py_TYPE(" << PYTHON_RETURN_VAR << ")->tp_name);\n"
<< returnStatement.statement << '\n' << outdent
if (func->type().isPointerToWrapperType())
s << " && " << PYTHON_RETURN_VAR << " != Py_None";
s << ") {\n" << indent
- << "Shiboken::Warnings::warnInvalidReturnValue(\""
- << func->ownerClass()->name() << "\", funcName, "
+ << "Shiboken::Warnings::warnInvalidReturnValue(ownerClassName, funcName, "
<< getVirtualFunctionReturnTypeName(func) << ", "
<< "Py_TYPE(" << PYTHON_RETURN_VAR << ")->tp_name);\n"
<< returnStatement.statement << '\n' << outdent
const CodeSnipList snips = func->hasInjectedCode()
? func->injectedCodeSnips() : CodeSnipList();
- QString prefix = wrapperName(func->ownerClass()) + u"::"_s;
- s << '\n' << functionSignature(func, prefix, QString(), Generator::SkipDefaultValues |
+ s << '\n' << functionSignature(func, wrapperName(func->ownerClass()), {},
+ Generator::SkipDefaultValues |
Generator::OriginalTypeDescription)
<< "\n{\n" << indent << sbkUnusedVariableCast("gil");
writeFuncNameVar(s, func, funcName);
-
+ s << "const char ownerClassName[] = \"" << func->ownerClass()->name() << "\";\n";
const auto returnStatement = virtualMethodReturn(api(), func,
func->modifications());
writeVirtualMethodPythonOverride(s, func, snips, returnStatement);
<< cpythonType << ", const_cast<void *>(cppIn), false, typeName);\n";
}
+// Either copy or generate a special converter using std::move for std::unique_ptr
+static bool hasCopyConverter(const AbstractMetaClassCPtr &metaClass)
+{
+ const auto &te = metaClass->typeEntry();
+ return !te->isObject() && (te->isCopyable() || te->isMovable());
+}
+
void CppGenerator::writeConverterFunctions(TextStream &s, const AbstractMetaClassCPtr &metaClass,
const GeneratorContext &classContext) const
{
writeCppToPythonFunction(s, c.toString(), sourceTypeName, targetTypeName);
// The conversions for an Object Type end here.
- if (!typeEntry->isValue() && !typeEntry->isSmartPointer()) {
+ if (!hasCopyConverter(metaClass)) {
s << '\n';
return;
}
c.clear();
- const bool isUniquePointer = classContext.forSmartPointer()
- && typeEntry->isUniquePointer();
-
- if (isUniquePointer) {
+ const bool needsMove = metaClass->typeEntry()->isMoveOnlyType();
+ if (needsMove) {
c << "auto *source = reinterpret_cast<" << typeName
<< " *>(const_cast<void *>(cppIn));\n";
} else {
}
c << "return Shiboken::Object::newObject(" << cpythonType
<< ", new " << globalScopePrefix(classContext) << classContext.effectiveClassName() << '('
- << (isUniquePointer ? "std::move(*source)" : "*source")
+ << (needsMove ? "std::move(*source)" : "*source")
<< "), true, true);";
writeCppToPythonFunction(s, c.toString(), sourceTypeName, targetTypeName);
s << '\n';
QString pyInVariable = u"pyIn"_s;
const QString outPtr = u"reinterpret_cast<"_s + typeName + u" *>(cppOut)"_s;
if (!classContext.forSmartPointer()) {
- c << '*' << outPtr << " = *"
- << cpythonWrapperCPtr(typeEntry, pyInVariable) << ';';
+ QString value = u'*' + cpythonWrapperCPtr(typeEntry, pyInVariable);
+ c << '*' << outPtr << " = " << (needsMove ? stdMove(value) : value) << ';';
} else {
auto ste = std::static_pointer_cast<const SmartPointerTypeEntry>(typeEntry);
const QString resetMethod = ste->resetMethod();
c << "ptr->" << resetMethod << "();\n";
const QString value = u'*' + cpythonWrapperCPtr(classContext.preciseType(), pyInVariable);
c << outdent << "else\n" << indent
- << "*ptr = " << (isUniquePointer ? stdMove(value) : value) << ';';
+ << "*ptr = " << (needsMove ? stdMove(value) : value) << ';';
}
writePythonToCppFunction(s, c.toString(), sourceTypeName, targetTypeName);
<< convertibleToCppFunctionName(sourceTypeName, targetTypeName) << ',' << '\n';
std::swap(targetTypeName, sourceTypeName);
s << cppToPythonFunctionName(sourceTypeName, targetTypeName);
- if (typeEntry->isValue() || typeEntry->isSmartPointer()) {
+
+ if (hasCopyConverter(metaClass)) {
s << ',' << '\n';
sourceTypeName = metaClass->name() + u"_COPY"_s;
s << cppToPythonFunctionName(sourceTypeName, targetTypeName);
registerConverterName::TypeId);
}
- if (!typeEntry->isValue() && !typeEntry->isSmartPointer())
+ if (!hasCopyConverter(metaClass))
return;
// Python to C++ copy (value, not pointer neither reference) conversion.
}
}
+// Non-constructible classes
+void CppGenerator::writeConstructorDummy(TextStream &s,
+ const GeneratorContext &classContext,
+ const char *errorMessageFunc)
+{
+ s << "static int " << cpythonConstructorName(classContext.metaClass())
+ << "(PyObject *, PyObject *, PyObject *)\n{\n" << indent
+ << "Shiboken::Errors::" << errorMessageFunc << "(\""
+ << classContext.metaClass()->name()
+ << "\");\nreturn -1;\n"
+ << outdent << "}\n\n";
+}
+
void CppGenerator::writeConstructorWrapper(TextStream &s, const OverloadData &overloadData,
const GeneratorContext &classContext) const
{
s << '\n';
- writeFunctionReturnErrorCheckSection(s, ErrorReturn::Default,
- hasReturnValue && !rfunc->isInplaceOperator());
-
if (hasReturnValue) {
if (rfunc->isInplaceOperator()) {
- s << "Py_INCREF(self);\nreturn self;\n";
+ s << "return Sbk_ReturnFromPython_Self(self);\n";
} else {
- s << "return " << PYTHON_RETURN_VAR << ";\n";
+ s << "return Sbk_ReturnFromPython_Result(" << PYTHON_RETURN_VAR <<");\n";
}
} else {
- s << "Py_RETURN_NONE;\n";
+ s << " return Sbk_ReturnFromPython_None();\n";
}
s<< outdent << "}\n\n";
return;
}
- AbstractMetaClassCPtr metaClass = context.metaClass();
+ const AbstractMetaClassCPtr &metaClass = context.metaClass();
const auto cppWrapper = context.metaClass()->cppWrapper();
// In the Python method, use the wrapper to access the protected
// functions.
if (!argType.typeEntry()->isCustom()) {
typeCheck = u'(' + pythonToCppConverterForArgumentName(argumentName)
+ u" = "_s + typeCheck + u"))"_s;
- if (!isNumber && isCppPrimitive(argType.typeEntry())) {
+ if (!isNumber
+ && argType.typeUsagePattern() == AbstractMetaType::PrimitivePattern // no array
+ && isCppPrimitive(argType.typeEntry())) {
typeCheck.prepend(cpythonCheckFunction(argType) + u'('
+ argumentName + u") && "_s);
}
+ ", "_L1
+ QString::number(nestedArrayTypes.constFirst().arrayElementCount())
+ u'>';
+ default:
+ break;
}
return {};
}
AbstractMetaClassCPtr ownerClass = func->ownerClass();
ComplexTypeEntryCPtr baseContainerType = ownerClass->typeEntry()->baseContainerType();
if (baseContainerType && baseContainerType == func->arguments().constFirst().type().typeEntry()
- && ownerClass->isCopyable()) {
+ && ownerClass->typeEntry()->isCopyable()) {
tck << '!' << cpythonCheckFunction(ownerClass->typeEntry())
<< pyArgName << ")\n" << indent << "&& " << outdent;
}
} else if (!injectCodeCallsFunc && !func->isUserAdded() && !hasConversionRule) {
// When an argument is removed from a method signature and no other means of calling
// the method are provided (as with code injection) the generator must abort.
- QString m;
- QTextStream(&m) << "No way to call '" << func->ownerClass()->name()
- << "::" << func->signature()
- << "' with the modifications described in the type system.";
- throw Exception(m);
+ throw Exception(msgCannotCall(func, argIdx, injectCodeCallsFunc, hasConversionRule));
}
removedArgs++;
continue;
}
void CppGenerator::writeCppToPythonFunction(TextStream &s, const QString &code, const QString &sourceTypeName,
- QString targetTypeName) const
+ const QString &targetTypeName) const
{
QString prettyCode = code;
: getFullTypeName(targetType.typeEntry());
c << "*reinterpret_cast<" << fullTypeName << " *>(cppOut) = "
<< fullTypeName << '('
- << (sourceType.isUniquePointer() ? stdMove(conversion) : conversion)
+ << (sourceType.useStdMove() ? stdMove(conversion) : conversion)
<< ");";
QString sourceTypeName = fixedCppTypeName(sourceType);
QString targetTypeName = fixedCppTypeName(targetType);
return;
}
+ Q_ASSERT(usePyArgs);
+
+ const auto count = args.size();
// PySide-535: Allow for empty dict instead of nullptr in PyPy
- s << "if (kwds && PyDict_Size(kwds) > 0) {\n" << indent;
- if (!force)
- s << "PyObject *value{};\n";
- s << "Shiboken::AutoDecRef kwds_dup(PyDict_Copy(kwds));\n";
- for (const AbstractMetaArgument &arg : args) {
+ s << "if (kwds && PyDict_Size(kwds) > 0)"; // {\n" << indent;
+ if (count == 0) {
+ s << indent << "\nerrInfo.reset(PyDict_Copy(kwds));\n" << outdent;
+ return;
+ }
+ s << " {\n" << indent
+ << "static const Shiboken::ArgumentNameIndexMapping mapping[" << count << "] = {";
+ for (qsizetype i = 0; i < count; ++i) {
+ const auto &arg = args.at(i);
const int pyArgIndex = arg.argumentIndex()
- - OverloadData::numberOfRemovedArguments(func, arg.argumentIndex());
- QString pyArgName = usePyArgs ? pythonArgsAt(pyArgIndex)
- : PYTHON_ARG;
- QString pyKeyName = u"key_"_s + arg.name();
- s << "static PyObject *const " << pyKeyName
- << " = Shiboken::String::createStaticString(\"" << arg.name() << "\");\n"
- << "if (PyDict_Contains(kwds, " << pyKeyName << ") != 0) {\n" << indent
- << "value = PyDict_GetItem(kwds, " << pyKeyName << ");\n"
- << "if (value != nullptr && " << pyArgName << " != nullptr ) {\n"
- << indent << "errInfo.reset(" << pyKeyName << ");\n"
- << "Py_INCREF(errInfo.object());\n"
- << "return " << returnErrorWrongArguments(overloadData, classContext, errorReturn)
- << ";\n" << outdent << "}\nif (value != nullptr) {\n" << indent
- << pyArgName << " = value;\nif (!";
+ - OverloadData::numberOfRemovedArguments(func, arg.argumentIndex());
+ if (i > 0)
+ s << ", ";
+ s << "{\"" << arg.name() << "\", " << pyArgIndex << '}';
+ }
+
+ s << "};\n";
+
+ const char *mappingFunc = func->isConstructor() && isQObject(func->ownerClass())
+ ? "parseConstructorKeywordArguments" : "parseKeywordArguments";
+ s << "if (!Shiboken::" << mappingFunc << "(kwds, mapping, "
+ << count << ", errInfo, " << PYTHON_ARGS << ')' << indent;
+ for (qsizetype i = 0; i < count; ++i) {
+ const auto &arg = args.at(i);
+ const int pyArgIndex = arg.argumentIndex()
+ - OverloadData::numberOfRemovedArguments(func, arg.argumentIndex());
const auto &type = arg.modifiedType();
+ const QString pyArgName = pythonArgsAt(pyArgIndex);
+ s << "\n|| ";
+ s << '(' << pyArgName << " != nullptr && !";
writeTypeCheck(s, type, pyArgName, isNumber(type.typeEntry()), {});
- s << ")\n" << indent
- << "return " << returnErrorWrongArguments(overloadData, classContext, errorReturn)
- << ";\n" << outdent << outdent
- << "}\nPyDict_DelItem(kwds_dup, " << pyKeyName << ");\n"
- << outdent << "}\n";
+ s << ')';
}
+ s << outdent << ") {\n" << indent
+ << "Py_XINCREF(errInfo.object());\n" // PYSIDE-3133, 0 if conversion fails
+ << "return " << returnErrorWrongArguments(overloadData, classContext, errorReturn)
+ << ';' << outdent << "\n}\n";;
+
// PYSIDE-1305: Handle keyword args correctly.
// Normal functions handle their parameters immediately.
// For constructors that are QObject, we need to delay that
// until extra keyword signals and properties are handled.
- s << "if (PyDict_Size(kwds_dup) > 0) {\n" << indent
- << "errInfo.reset(kwds_dup.release());\n";
- if (!(func->isConstructor() && isQObject(func->ownerClass()))) {
- s << "return " << returnErrorWrongArguments(overloadData, classContext, errorReturn)
- << ";\n";
- } else {
- s << "// fall through to handle extra keyword signals and properties\n";
- }
- s << outdent << "}\n"
- << outdent << "}\n";
+ s << outdent << "}\n";
}
QString CppGenerator::argumentNameFromIndex(const ApiExtractorResult &api,
return PYTHON_SELF_VAR;
case 0:
return PYTHON_RETURN_VAR;
- case 1: { // Single argument?
- OverloadData data(getFunctionGroups(func->implementingClass()).value(func->name()), api);
- if (!data.pythonFunctionWrapperUsesListOfArguments())
- return PYTHON_ARG;
+ case 1: // Single argument?
+ if (!func->isConstructor()) {
+ OverloadData data(getFunctionGroups(func->implementingClass()).value(func->name()), api);
+ if (!data.pythonFunctionWrapperUsesListOfArguments())
+ return PYTHON_ARG;
+ }
+ break;
+ default:
break;
- }
}
return pythonArgsAt(argIndex - 1);
}
return str;
}
+static inline bool isDocString(const DocModification &d)
+{
+ return d.target() == DocumentationTarget::DocString;
+}
+
+static QString docString(const AbstractMetaClassCPtr &metaClass)
+{
+ const auto docModifs = metaClass->typeEntry()->docModifications();
+ auto it = std::find_if(docModifs.cbegin(), docModifs.cend(), isDocString);
+ return it != docModifs.cend() ? it->code().trimmed() : QString{};
+}
+
void CppGenerator::writeClassDefinition(TextStream &s,
const AbstractMetaClassCPtr &metaClass,
const GeneratorContext &classContext)
{
- QString tp_init;
QString tp_new;
QString tp_dealloc;
QString tp_hash;
QString tp_call;
const QString className = chopType(cpythonTypeName(metaClass));
- AbstractMetaFunctionCList ctors;
- const auto &allCtors = metaClass->queryFunctions(FunctionQueryOption::AnyConstructor);
- for (const auto &f : allCtors) {
- if (!f->isPrivate() && !f->isModifiedRemoved()
- && f->functionType() != AbstractMetaFunction::MoveConstructorFunction) {
- ctors.append(f);
- }
- }
bool onlyPrivCtor = !metaClass->hasNonPrivateConstructor();
tp_dealloc = metaClass->hasPrivateDestructor() ?
u"SbkDeallocWrapperWithPrivateDtor"_s :
u"Sbk_object_dealloc /* PYSIDE-832: Prevent replacement of \"0\" with subtype_dealloc. */"_s;
- tp_init.clear();
} else {
tp_dealloc = isQApp
? u"&SbkDeallocQAppWrapper"_s : u"&SbkDeallocWrapper"_s;
- if (!onlyPrivCtor && !ctors.isEmpty())
- tp_init = cpythonConstructorName(metaClass);
}
const AttroCheck attroCheck = checkAttroFunctionNeeds(metaClass);
<< pyTypeSlotEntry("Py_tp_iternext", m_tpFuncs.value(u"__next__"_s))
<< pyTypeSlotEntry("Py_tp_methods", className + u"_methods"_s)
<< pyTypeSlotEntry("Py_tp_getset", tp_getset)
- << pyTypeSlotEntry("Py_tp_init", tp_init)
+ << pyTypeSlotEntry("Py_tp_init", cpythonConstructorName(metaClass))
<< pyTypeSlotEntry("Py_tp_new", tp_new);
if (supportsSequenceProtocol(metaClass)) {
s << "// type supports sequence protocol\n";
s << "// type supports number protocol\n";
writeTypeAsNumberDefinition(s, metaClass);
}
+
+ const QString ds = docString(metaClass);
+ if (!ds.isEmpty()) {
+ s << "{Py_tp_doc, " << outdent
+ << "const_cast<char *>(R\"DS(" << ds << ")DS\")" << indent << "},\n";
+ }
+
s << "{0, " << NULL_PTR << "}\n" << outdent << "};\n";
- int packageLevel = packageName().count(u'.') + 1;
+ const auto packageLevel = packageName().count(u'.') + 1;
s << "static PyType_Spec " << className << "_spec = {\n" << indent
<< '"' << packageLevel << ':' << getClassTargetFullName(metaClass) << "\",\n"
<< "sizeof(SbkObject),\n0,\n" << tp_flags << ",\n"
TextStream &signatureStream,
const GeneratorContext &context)
{
- const auto metaClass = context.metaClass();
+ const auto &metaClass = context.metaClass();
const QString className = chopType(cpythonTypeName(metaClass));
const QString funcName = className + u"__copy__"_s;
- signatureStream << fullPythonClassName(metaClass) << ".__copy__(self)->typing.Self\n";
+ // PYSIDE-3135 replace _Self by Self when the minimum Python version is 3.11
+ signatureStream << fullPythonClassName(metaClass) << ".__copy__(self)->typing._Self\n";
definitionStream << PyMethodDefEntry{u"__copy__"_s, funcName, {"METH_NOARGS"_ba}, {}}
<< ",\n";
const QList<AbstractMetaFunctionCList> &groupedFuncs =
filterGroupedOperatorFunctions(metaClass, OperatorQueryOption::ComparisonOp);
for (const AbstractMetaFunctionCList &overloads : groupedFuncs) {
- const auto rfunc = overloads[0];
+ const auto &rfunc = overloads.constFirst();
const auto op = rfunc->comparisonOperatorType().value();
s << "case " << AbstractMetaFunction::pythonRichCompareOpCode(op)
QTextStream s(&result);
auto metaType = arg.type();
- if (auto viewOn = metaType.viewOn())
+ if (const auto *viewOn = metaType.viewOn())
metaType = *viewOn;
s << arg.name() << ':';
for (const auto &spec : metaClass->propertySpecs()) {
auto typeEntry = spec.typeEntry();
QString text;
- if (typeEntry->isFlags()) {
+ if (typeEntry->isEnum()) {
+ const auto ete = std::static_pointer_cast<const EnumTypeEntry>(typeEntry);
+ text = ete->qualifiedTargetLangName();
+ } else if (typeEntry->isFlags()) {
const auto fte = std::static_pointer_cast<const FlagsTypeEntry>(typeEntry);
text = fte->originator()->qualifiedTargetLangName();
} else {
text = typeEntry->qualifiedCppName();
}
- auto &inst = spec.type().instantiations();
+ const auto &inst = spec.type().instantiations();
if (!inst.isEmpty()) {
text += u'[';
for (qsizetype i = 0, size = inst.size(); i < size; ++i) {
const QByteArray cppSig =
QMetaObject::normalizedType(qPrintable(metaType.cppSignature()));
if ((origType != cppSig) && (!metaType.isFlags())) {
- qCWarning(lcShiboken).noquote().nospace()
- << "Typedef used on signal " << metaClass->qualifiedCppName() << "::"
- << cppSignal->signature();
- }
+ QString msg = "Typedef used on signal "_L1 + metaClass->qualifiedCppName()
+ + "::"_L1 + cppSignal->signature();
+ ReportHandler::addGeneralMessage(msg);
+ }
}
}
for (auto base : baseClasses) {
for (; base != nullptr; base = base->baseClass()) { // Find a type that is not disabled.
const auto ct = base->typeEntry()->codeGeneration();
- if (ct == TypeEntry::GenerateCode || ct == TypeEntry::GenerateForSubclass)
+ if (ct == TypeEntry::GenerateCode || ct == TypeEntry::GenerateForSubclass) {
+ result.append(base->typeEntry());
break;
+ }
}
- result.append(base->typeEntry());
}
return result;
}
// 8:wrapperflags
QByteArrayList wrapperFlags;
if (enc)
- wrapperFlags.append(QByteArrayLiteral("Shiboken::ObjectType::WrapperFlags::InnerClass"));
+ wrapperFlags.append("Shiboken::ObjectType::WrapperFlags::InnerClass"_ba);
if (metaClass->deleteInMainThread())
- wrapperFlags.append(QByteArrayLiteral("Shiboken::ObjectType::WrapperFlags::DeleteInMainThread"));
+ wrapperFlags.append("Shiboken::ObjectType::WrapperFlags::DeleteInMainThread"_ba);
if (classTypeEntry->isValue())
wrapperFlags.append("Shiboken::ObjectType::WrapperFlags::Value"_ba);
if (wrapperFlags.isEmpty())
}
for (const AbstractMetaEnum &metaEnum : metaClass->enums()) {
- if (!metaEnum.isPrivate() && !metaEnum.isAnonymous()) {
+ if (!metaEnum.isPrivate() && !metaEnum.isAnonymous()
+ && metaEnum.typeEntry()->qtMetaTypeRegistration() != TypeSystem::QtMetaTypeRegistration::Disabled) {
for (const QString &name : std::as_const(nameVariants)) {
s << "qRegisterMetaType< " << m_gsp
<< metaEnum.typeEntry()->qualifiedCppName() << " >(\""
void CppGenerator::writeDefaultSequenceMethods(TextStream &s,
const GeneratorContext &context) const
{
- const auto metaClass = context.metaClass();
+ const auto &metaClass = context.metaClass();
ErrorReturn errorReturn = ErrorReturn::Zero;
// __len__
const GeneratorContext &context,
uint indirections)
{
- const auto metaClass = context.metaClass();
+ const auto &metaClass = context.metaClass();
QString funcName = writeReprFunctionHeader(s, context);
writeCppSelfDefinition(s, context);
s << R"(QBuffer buffer;
#include "include.h"
#include "modifications_typedefs.h"
-#include <QtCore/QFlags>
-#include <QtCore/QSet>
-#include <QtCore/QHash>
+#include <QtCore/qflags.h>
+#include <QtCore/qset.h>
+#include <QtCore/qhash.h>
#include <memory>
#include <utility>
void writeVirtualMethodCppCall(TextStream &s, const AbstractMetaFunctionCPtr &func,
const QString &funcName, const QList<CodeSnip> &snips,
const AbstractMetaArgument *lastArg, const TypeEntryCPtr &retType,
- const QString &returnStatement, bool hasGil) const;
+ const QString &returnStatement,
+ bool ownsGil, bool hasGilVar) const;
static VirtualMethodReturn virtualMethodReturn(const ApiExtractorResult &api,
const AbstractMetaFunctionCPtr &func,
void writeConstructorWrapper(TextStream &s,
const OverloadData &overloadData,
const GeneratorContext &classContext) const;
+ static void writeConstructorDummy(TextStream &s, const GeneratorContext &classContext,
+ const char *errorMessageFunc);
void writeMethodWrapper(TextStream &s, const OverloadData &overloadData,
const GeneratorContext &classContext) const;
void writeMethodWrapper(TextStream &s, TextStream &definitionStream,
/// Writes a C++ to Python conversion function.
void writeCppToPythonFunction(TextStream &s, const QString &code, const QString &sourceTypeName,
- QString targetTypeName = QString()) const;
+ const QString &targetTypeName = {}) const;
void writeCppToPythonFunction(TextStream &s, const CustomConversionPtr &customConversion) const;
void writeCppToPythonFunction(TextStream &s, const AbstractMetaType &containerType) const;
/// Main target type name of a container (for naming the functions).
#include "textstream.h"
#include "typedatabase.h"
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
#include <algorithm>
#include <overloaddata.h>
#include <smartpointertypeentry.h>
-#include <QtCore/QDebug>
+#include <QtCore/qdebug.h>
using namespace Qt::StringLiterals;
const GeneratorContext &classContext)
{
s.setLanguage(TextStream::Language::Cpp);
- AbstractMetaClassCPtr metaClass = classContext.metaClass();
+ const AbstractMetaClassCPtr &metaClass = classContext.metaClass();
const auto typeEntry = std::static_pointer_cast<const SmartPointerTypeEntry>(metaClass->typeEntry());
const bool hasPointeeClass = classContext.pointeeClass() != nullptr;
const auto smartPointerType = typeEntry->smartPointerType();
static const char selfPointeeVar[] = "cppSelfPointee";
static const char cppArg0PointeeVar[] = "cppArg0Pointee";
- const auto metaClass = context.metaClass();
+ const auto &metaClass = context.metaClass();
QString baseName = cpythonBaseName(metaClass);
writeRichCompareFunctionHeader(s, baseName, context);
const BoolCastFunctionOptional &boolCast)
{
Q_ASSERT(context.forSmartPointer());
- const auto metaClass = context.metaClass();
+ const auto &metaClass = context.metaClass();
writeGetattroDefinition(s, metaClass);
s << "PyObject *tmp = PyObject_GenericGetAttr(self, name);\n"
<< "if (tmp)\n" << indent << "return tmp;\n" << outdent
#ifndef CTYPENAMES_H
#define CTYPENAMES_H
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
constexpr auto boolT = QLatin1StringView("bool");
constexpr auto intT = QLatin1StringView("int");
#include <messages.h>
#include <typesystem.h>
-#include <QtCore/QDebug>
-#include <QtCore/QSet>
+#include <QtCore/qdebug.h>
+#include <QtCore/qset.h>
static bool isCppPrimitiveString(const AbstractMetaType &type)
{
#ifndef GENERATORARGUMENT_H
#define GENERATORARGUMENT_H
-#include <QtCore/QFlags>
+#include <QtCore/qflags.h>
#include <QtCore/qobjectdefs.h>
QT_FORWARD_DECLARE_CLASS(QDebug)
#ifndef GENERATORSTRINGS_H
#define GENERATORSTRINGS_H
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
QString CPP_ARG_N(int i);
QString CPP_ARG_REMOVED(int i);
#include <algorithm>
#include <set>
-#include <QtCore/QDir>
-#include <QtCore/QTextStream>
-#include <QtCore/QVariant>
-#include <QtCore/QDebug>
+#include <QtCore/qdir.h>
+#include <QtCore/qtextstream.h>
+#include <QtCore/qvariant.h>
+#include <QtCore/qdebug.h>
using namespace Qt::StringLiterals;
+using FunctionSet = QSet<AbstractMetaFunctionCPtr>;
+
struct IndexValue
{
QString name; // "SBK_..."
QString comment;
};
+// Returns the methods hidden from base classes
+static FunctionSet getHiddenOverloads(const AbstractMetaFunctionCPtr &func)
+{
+ FunctionSet result;
+ // Check if this method hide other methods in base classes
+ auto hiddenBy = [&func](const AbstractMetaFunctionCPtr &f) {
+ return f != func && !f->isConstructor() && !f->isPrivate() && !f->isVirtual()
+ && !f->isUserAdded() && !f->isAbstract() && !f->isStatic()
+ && f->name() == func->name();
+ };
+ const auto &functions = func->ownerClass()->functions();
+ std::copy_if(functions.cbegin(), functions.cend(),
+ std::inserter(result, result.end()), hiddenBy);
+ return result;
+}
+
TextStream &operator<<(TextStream &s, const IndexValue &iv)
{
s << " " << AlignedField(iv.name, 56) << " = " << iv.value << ',';
const AbstractMetaClassCPtr &metaClass)
{
s << wrapperName(metaClass) << "(const " << metaClass->qualifiedCppName()
- << "& self) : " << metaClass->qualifiedCppName() << "(self)\n{\n}\n\n";
+ << "& self) : " << metaClass->qualifiedCppName() << "(self)\n{\n}\n";
}
-static void writeProtectedEnums(TextStream &s, const AbstractMetaClassCPtr &metaClass)
+static void writeProtectedEnumsHelper(TextStream &s, const AbstractMetaClassCPtr &metaClass)
{
const QString name = metaClass->qualifiedCppName();
for (const auto &e : metaClass->enums()) {
doGenerateClass(s, classContext);
}
+QString HeaderGenerator::headerGuard(const QString &className)
+{
+ return "SBK_"_L1 + getFilteredCppSignatureString(className.toUpper()) + "_H"_L1;
+}
+
void HeaderGenerator::doGenerateClass(TextStream &s, const GeneratorContext &classContext) const
{
- const AbstractMetaClassCPtr metaClass = classContext.metaClass();
+ const AbstractMetaClassCPtr &metaClass = classContext.metaClass();
// write license comment
s << licenseComment();
QString wrapperName = classContext.effectiveClassName();
- QString outerHeaderGuard = getFilteredCppSignatureString(wrapperName);
+ QString outerHeaderGuard = headerGuard(classContext.effectiveClassName());
// Header
- s << "#ifndef SBK_" << outerHeaderGuard << "_H\n";
- s << "#define SBK_" << outerHeaderGuard << "_H\n\n";
+ s << "#ifndef " << outerHeaderGuard << '\n'
+ << "#define " << outerHeaderGuard << "\n\n";
if (!avoidProtectedHack())
s << protectedHackDefine;
if (classContext.useWrapper())
writeWrapperClass(s, wrapperName, classContext);
- s << "#endif // SBK_" << outerHeaderGuard << "_H\n\n";
+ s << "#endif // " << outerHeaderGuard << '\n';
}
void HeaderGenerator::writeWrapperClass(TextStream &s,
const QString &wrapperName,
const GeneratorContext &classContext) const
{
- const auto metaClass = classContext.metaClass();
+ const auto &metaClass = classContext.metaClass();
if (avoidProtectedHack()) {
const auto includeGroups = classIncludes(metaClass);
s << includeGroup;
}
+ s << "namespace Shiboken { struct AutoDecRef; class GilState; }\n\n";
+
if (usePySideExtensions() && isQObject(metaClass))
s << "namespace PySide { class DynamicQMetaObject; }\n\n";
const GeneratorContext &classContext) const
{
const QString wrapperName = classContext.effectiveClassName();
- const QString innerHeaderGuard =
- getFilteredCppSignatureString(wrapperName).toUpper();
+ const QString innerHeaderGuard = headerGuard(wrapperName);
- s << "# ifndef SBK_" << innerHeaderGuard << "_H\n"
- << "# define SBK_" << innerHeaderGuard << "_H\n\n"
+ s << "# ifndef " << innerHeaderGuard << '\n'
+ << "# define " << innerHeaderGuard << "\n\n"
<< "// Inherited base class:\n";
writeWrapperClassDeclaration(s, wrapperName, classContext);
- s << "# endif // SBK_" << innerHeaderGuard << "_H\n\n";
+ s << "# endif // " << innerHeaderGuard << "\n\n";
}
void HeaderGenerator::writeWrapperClassDeclaration(TextStream &s,
const QString &wrapperName,
const GeneratorContext &classContext) const
{
- const AbstractMetaClassCPtr metaClass = classContext.metaClass();
+ const AbstractMetaClassCPtr &metaClass = classContext.metaClass();
const auto typeEntry = metaClass->typeEntry();
- InheritedOverloadSet inheritedOverloads;
// Class
s << "class " << wrapperName
<< " : public " << metaClass->qualifiedCppName()
- << "\n{\npublic:\n" << indent
- << wrapperName << "(const " << wrapperName << " &) = delete;\n"
- << wrapperName << "& operator=(const " << wrapperName << " &) = delete;\n"
- << wrapperName << '(' << wrapperName << " &&) = delete;\n"
- << wrapperName << "& operator=(" << wrapperName << " &&) = delete;\n\n";
-
- // Make protected enums accessible
- if (avoidProtectedHack()) {
- recurseClassHierarchy(metaClass, [&s] (const AbstractMetaClassCPtr &metaClass) {
- writeProtectedEnums(s, metaClass);
- return false;
- });
- }
+ << "\n{\npublic:\n" << indent;
- if (avoidProtectedHack() && metaClass->hasProtectedFields()) {
- s << "\n// Make protected fields accessible\n";
- const QString name = metaClass->qualifiedCppName();
- for (const auto &f : metaClass->fields()) {
- if (f.isProtected())
- s << "using " << name << "::" << f.originalName() << ";\n";
- }
- s << '\n';
- }
+ writeProtectedEnums(s, classContext);
+ writeSpecialFunctions(s, wrapperName, classContext);
int maxOverrides = 0;
for (const auto &func : metaClass->functions()) {
const auto generation = functionGeneration(func);
- writeFunction(s, func, &inheritedOverloads, generation);
+ writeFunction(s, func, generation);
// PYSIDE-803: Build a boolean cache for unused overrides.
if (generation.testFlag(FunctionGenerationFlag::VirtualMethod))
maxOverrides++;
}
- //destructor
- // PYSIDE-504: When C++ 11 is used, then the destructor must always be declared.
- if (!avoidProtectedHack() || !metaClass->hasPrivateDestructor()
- || alwaysGenerateDestructorDeclaration()) {
- if (avoidProtectedHack() && metaClass->hasPrivateDestructor())
- s << "// C++11: need to declare (unimplemented) destructor because "
- "the base class destructor is private.\n";
- s << '~' << wrapperName << "()";
- if (metaClass->hasVirtualDestructor())
- s << " override";
- s << ";\n";
- }
-
writeClassCodeSnips(s, typeEntry->codeSnips(),
TypeSystem::CodeSnipPositionDeclaration, TypeSystem::NativeCode,
classContext);
)";
}
- if (!inheritedOverloads.isEmpty()) {
- s << "// Inherited overloads, because the using keyword sux\n";
- for (const auto &func : std::as_const(inheritedOverloads))
- writeMemberFunctionWrapper(s, func);
- }
-
if (usePySideExtensions())
s << "static void pysideInitQtMetaTypes();\n";
Q_ASSERT(maxOverrides > 0 || !needsMethodCache);
if (needsMethodCache)
- s << "void resetPyMethodCache();\n";
+ s << "\nvoid resetPyMethodCache();\n";
- s << outdent << "private:\n" << indent;
+ writeProtectedFields(s, classContext);
+
+ s << outdent << "\nprivate:\n" << indent;
if (!metaClass->userAddedPythonOverrides().isEmpty()) {
for (const auto &f : metaClass->userAddedPythonOverrides())
s << outdent << "};\n\n";
}
+void HeaderGenerator::writeSpecialFunctions(TextStream &s, const QString &wrapperName,
+ const GeneratorContext &classContext) const
+{
+ const AbstractMetaClassCPtr &metaClass = classContext.metaClass();
+
+ const auto &wrapperConstructors = ShibokenGenerator::getWrapperConstructors(metaClass);
+ for (const auto &func : wrapperConstructors)
+ writeConstructor(s, func);
+ s << wrapperName << "(const " << wrapperName << " &) = delete;\n";
+ // Special inline copy CT (Wrapper from metaClass)
+ const auto ©Constructors = metaClass->queryFunctions(FunctionQueryOption::CopyConstructor);
+ if (!copyConstructors.isEmpty()) {
+ auto generation = functionGeneration(copyConstructors.constFirst());
+ if (generation.testFlag(FunctionGenerationFlag::WrapperSpecialCopyConstructor))
+ writeCopyCtor(s, metaClass);
+ }
+ s << wrapperName << "& operator=(const " << wrapperName << " &) = delete;\n"
+ << wrapperName << '(' << wrapperName << " &&) = delete;\n"
+ << wrapperName << "& operator=(" << wrapperName << " &&) = delete;\n";
+
+ // destructor
+ // PYSIDE-504: When C++ 11 is used, then the destructor must always be declared.
+ if (!avoidProtectedHack() || !metaClass->hasPrivateDestructor()
+ || alwaysGenerateDestructorDeclaration()) {
+ if (avoidProtectedHack() && metaClass->hasPrivateDestructor())
+ s << "// C++11: need to declare (unimplemented) destructor because "
+ "the base class destructor is private.\n";
+ s << '~' << wrapperName << "()";
+ if (metaClass->hasVirtualDestructor())
+ s << " override";
+ s << ";\n\n";
+ }
+}
+
+void HeaderGenerator::writeProtectedEnums(TextStream &s,
+ const GeneratorContext &classContext)
+{
+ // Make protected enums accessible
+ if (avoidProtectedHack()) {
+ const auto lastPos = s.pos();
+ const AbstractMetaClassCPtr &metaClass = classContext.metaClass();
+ recurseClassHierarchy(metaClass, [&s] (const AbstractMetaClassCPtr &metaClass) {
+ writeProtectedEnumsHelper(s, metaClass);
+ return false;
+ });
+ if (s.pos() != lastPos)
+ s << '\n';
+ }
+}
+
+void HeaderGenerator::writeProtectedFields(TextStream &s,
+ const GeneratorContext &classContext)
+{
+ const AbstractMetaClassCPtr &metaClass = classContext.metaClass();
+ if (avoidProtectedHack() && metaClass->hasProtectedFields()) {
+ s << "\n// Make protected fields accessible\n";
+ const QString name = metaClass->qualifiedCppName();
+ for (const auto &f : metaClass->fields()) {
+ if (f.isProtected())
+ s << "using " << name << "::" << f.originalName() << ";\n";
+ }
+ }
+}
+
// Write an inline wrapper around a function
void HeaderGenerator::writeMemberFunctionWrapper(TextStream &s,
const AbstractMetaFunctionCPtr &func,
enumTypeEntry = type.typeEntry();
if (enumTypeEntry) {
s << type.cppSignature() << '(' << arg.name() << ')';
- } else if (type.passByValue() && type.isUniquePointer()) {
+ } else if (type.useStdMove()) {
s << stdMove(arg.name());
} else {
s << arg.name();
s << "); }\n";
}
+void HeaderGenerator::writeConstructor(TextStream &s, const AbstractMetaFunctionCPtr &func) const
+{
+ Options option = func->hasSignatureModifications()
+ ? Generator::OriginalTypeDescription : Generator::NoOption;
+ s << functionSignature(func, {}, {}, option) << ";\n";
+}
+
void HeaderGenerator::writeFunction(TextStream &s, const AbstractMetaFunctionCPtr &func,
- InheritedOverloadSet *inheritedOverloads,
FunctionGeneration generation) const
{
-
- // do not write copy ctors here.
- if (generation.testFlag(FunctionGenerationFlag::WrapperSpecialCopyConstructor)) {
- writeCopyCtor(s, func->ownerClass());
- return;
- }
-
if (generation.testFlag(FunctionGenerationFlag::ProtectedWrapper))
writeMemberFunctionWrapper(s, func, u"_protected"_s);
- if (generation.testFlag(FunctionGenerationFlag::WrapperConstructor)) {
- Options option = func->hasSignatureModifications()
- ? Generator::OriginalTypeDescription : Generator::NoOption;
- s << functionSignature(func, {}, {}, option) << ";\n";
+ if (!generation.testFlag(FunctionGenerationFlag::VirtualMethod))
return;
- }
- const bool isVirtual = generation.testFlag(FunctionGenerationFlag::VirtualMethod);
- if (isVirtual) {
- s << functionSignature(func, {}, {}, Generator::OriginalTypeDescription)
- << " override;\n";
- }
+ const Options options = Generator::OriginalTypeDescription;
+ s << functionSignature(func, {}, {}, options)
+ << " override;\n";
- // Check if this method hide other methods in base classes
- if (isVirtual) {
- for (const auto &f : func->ownerClass()->functions()) {
- if (f != func
- && !f->isConstructor()
- && !f->isPrivate()
- && !f->isVirtual()
- && !f->isAbstract()
- && !f->isStatic()
- && f->name() == func->name()) {
- inheritedOverloads->insert(f);
- }
- }
+ if (!getReusedOverridenFunctions(func->ownerClass()).contains(func)) {
+ s << functionSignature(func, {}, {}, options | Generator::PythonOverrideImplementation)
+ << ";\n";
+ }
- // TODO: when modified an abstract method ceases to be virtual but stays abstract
- //if (func->isModifiedRemoved() && func->isAbstract()) {
- //}
+ const auto &hiddenOverloads = getHiddenOverloads(func);
+ if (!hiddenOverloads.isEmpty()) {
+ s << "// Inherited overloads, because the using keyword sux\n";
+ for (const auto &func : hiddenOverloads)
+ writeMemberFunctionWrapper(s, func);
+ s << '\n';
}
+
+ // TODO: when modified an abstract method ceases to be virtual but stays abstract
+ //if (func->isModifiedRemoved() && func->isAbstract()) {
}
// Find equivalent typedefs "using Foo=QList<int>", "using Bar=QList<int>"
#include "include.h"
#include "modifications_typedefs.h"
-#include <QtCore/QList>
-#include <QtCore/QSet>
+#include <QtCore/qlist.h>
+#include <QtCore/qset.h>
struct IndexValue;
class AbstractMetaFunction;
bool finishGeneration() override;
private:
- using InheritedOverloadSet = QSet<AbstractMetaFunctionCPtr>;
using IndexValues = QList<IndexValue>;
+ static QString headerGuard(const QString &className);
void doGenerateClass(TextStream &s, const GeneratorContext &classContext) const;
IndexValues collectTypeIndexes(const AbstractMetaClassCList &classList);
IndexValues collectConverterIndexes() const;
static void writeCopyCtor(TextStream &s, const AbstractMetaClassCPtr &metaClass);
- void writeFunction(TextStream &s,
- const AbstractMetaFunctionCPtr &func,
- InheritedOverloadSet *inheritedOverloads,
+ void writeConstructor(TextStream &s, const AbstractMetaFunctionCPtr &func) const;
+ void writeFunction(TextStream &s, const AbstractMetaFunctionCPtr &func,
FunctionGeneration generation) const;
static void writeSbkTypeFunction(TextStream &s, const AbstractMetaEnum &cppEnum);
static void writeSbkTypeFunction(TextStream &s, const AbstractMetaClassCPtr &cppClass);
void writeWrapperClassDeclaration(TextStream &s,
const QString &wrapperName,
const GeneratorContext &classContext) const;
+ static void writeProtectedEnums(TextStream &s, const GeneratorContext &classContext);
+ void writeSpecialFunctions(TextStream &s, const QString &wrapperName,
+ const GeneratorContext &classContext) const;
+ static void writeProtectedFields(TextStream &s,
+ const GeneratorContext &classContext);
+
void writeWrapperClass(TextStream &s, const QString &wrapperName, const GeneratorContext &classContext) const;
void writeInheritedWrapperClassDeclaration(TextStream &s,
const GeneratorContext &classContext) const;
#include "qtcompat.h"
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QTemporaryFile>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qtemporaryfile.h>
#include <algorithm>
#include <utility>
static QString getTypeName(const AbstractMetaType &type)
{
TypeEntryCPtr typeEntry = type.typeEntry();
- if (typeEntry->isPrimitive())
- typeEntry = basicReferencedTypeEntry(typeEntry);
- QString typeName = typeEntry->name();
+ QString typeName = type.basicPrimitiveName();
if (typeEntry->isContainer()) {
QStringList types;
- for (const auto &cType : type.instantiations()) {
- TypeEntryCPtr typeEntry = cType.typeEntry();
- if (typeEntry->isPrimitive())
- typeEntry = basicReferencedTypeEntry(typeEntry);
- types << typeEntry->name();
- }
+ for (const auto &cType : type.instantiations())
+ types << cType.basicPrimitiveName();
typeName += u'<' + types.join(u',') + u" >"_s;
}
return typeName;
// and being PointF implicitly convertible from Point, an list<T> instantiation with T
// as Point must come before the PointF instantiation, or else list<Point> will never
// be called. In the case of primitive types, list<double> must come before list<int>.
- if (instantiation.isPrimitive() && (signedIntegerPrimitives.contains(instantiation.name()))) {
+ if (instantiation.isPrimitive()
+ && signedIntegerPrimitives.contains(instantiation.basicPrimitiveName())) {
for (const QString &primitive : std::as_const(nonIntegerPrimitives))
graph.addNode(getImplicitConversionTypeName(ov->argType(), instantiation, nullptr, primitive));
} else {
if (!graph.containsEdge(targetTypeEntryName, convertible)) // Avoid cyclic dependency.
graph.addEdge(convertible, targetTypeEntryName);
- if (instantiation.isPrimitive() && (signedIntegerPrimitives.contains(instantiation.name()))) {
+ if (instantiation.isPrimitive()
+ && signedIntegerPrimitives.contains(instantiation.basicPrimitiveName())) {
for (const QString &primitive : std::as_const(nonIntegerPrimitives)) {
QString convertibleTypeName =
getImplicitConversionTypeName(ov->argType(), instantiation, nullptr, primitive);
#include <apiextractorresult.h>
#include <abstractmetaargument.h>
-#include <QtCore/QBitArray>
-#include <QtCore/QList>
+#include <QtCore/qbitarray.h>
+#include <QtCore/qlist.h>
#include <memory>
--- /dev/null
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "overridecacheentry.h"
+
+#include <abstractmetaargument.h>
+#include <abstractmetatype.h>
+
+OverrideCacheEntry::OverrideCacheEntry(const AbstractMetaFunctionCPtr &f) :
+ m_func(f), m_modifications(f->modifications())
+{
+ m_types.reserve(1 + m_func->arguments().size());
+ m_types.append(m_func->type());
+ for (const auto &arg: m_func->arguments())
+ m_types.append(arg.type());
+}
+
+bool OverrideCacheEntry::equals(const OverrideCacheEntry &rhs) const noexcept
+{
+ return m_types == rhs.m_types && m_modifications == rhs.m_modifications;
+}
+
+size_t OverrideCacheEntry::hashValue(size_t seed) const noexcept
+{
+ return qHashMulti(seed,
+ qHashRange(m_types.cbegin(), m_types.cend(), seed),
+ qHashRange(m_modifications.cbegin(), m_modifications.cend(), seed));
+}
--- /dev/null
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef OVERRIDECACHEENTRY_H
+#define OVERRIDECACHEENTRY_H
+
+#include <abstractmetafunction.h>
+#include <modifications.h>
+
+#include <QtCore/qhash.h>
+
+// Cache a (virtual function) by types and modifications for reusing Python
+// override code.
+class OverrideCacheEntry
+{
+public:
+ explicit OverrideCacheEntry(const AbstractMetaFunctionCPtr &f);
+
+ const AbstractMetaFunctionCPtr &function() const { return m_func; }
+
+ size_t hashValue(size_t seed) const noexcept;
+
+private:
+ bool equals(const OverrideCacheEntry &rhs) const noexcept;
+
+ friend bool comparesEqual(const OverrideCacheEntry &lhs,
+ const OverrideCacheEntry &rhs) noexcept
+ { return lhs.equals(rhs); }
+
+ Q_DECLARE_EQUALITY_COMPARABLE(OverrideCacheEntry)
+
+ AbstractMetaFunctionCPtr m_func;
+ QList<AbstractMetaType> m_types;
+ FunctionModificationList m_modifications;
+};
+
+inline size_t qHash(const OverrideCacheEntry &e, size_t seed = 0) noexcept
+{
+ return e.hashValue(seed);
+}
+
+#endif // OVERRIDECACHEENTRY_H
#ifndef PYTYPENAMES_H
#define PYTYPENAMES_H
-#include <QtCore/QString>
+#include <QtCore/qstring.h>
constexpr auto pyBoolT = QLatin1StringView ("PyBool");
constexpr auto pyFloatT = QLatin1StringView ("PyFloat");
#include <abstractmetafunction.h>
#include <abstractmetalang.h>
#include <abstractmetalang_helpers.h>
+#include "overridecacheentry.h"
#include <usingmember.h>
#include <exception.h>
#include <messages.h>
#include "qtcompat.h"
-#include <QtCore/QDir>
-#include <QtCore/QDebug>
-#include <QtCore/QRegularExpression>
+#include <QtCore/qdir.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qset.h>
#include <algorithm>
#include <limits>
return CPP_ARG_REMOVED_PREFIX + QString::number(i);
}
+static QSet<OverrideCacheEntry> pythonOverrideCache;
+
const char *const METHOD_DEF_SENTINEL = "{nullptr, nullptr, 0, nullptr} // Sentinel\n";
const char *const PYTHON_TO_CPPCONVERSION_STRUCT = "Shiboken::Conversions::PythonToCppConversion";
struct GeneratorClassInfoCacheEntry
{
ShibokenGenerator::FunctionGroups functionGroups;
+ AbstractMetaFunctionCList constructors;
+ AbstractMetaFunctionCList wrapperConstructors;
QList<AbstractMetaFunctionCList> numberProtocolOperators;
BoolCastFunctionOptional boolCastFunctionO;
ShibokenGenerator::AttroCheck attroCheck;
+ // Maps a virtual function to an equivalent one for resuing the override implementation
+ ShibokenGenerator::FunctionMapping reusedOverrides;
};
using GeneratorClassInfoCache = QHash<AbstractMetaClassCPtr, GeneratorClassInfoCacheEntry>;
return fullClassName;
}
+// Non-smartpointer classes only (see comment at fileNameForContextHelper)
+QString ShibokenGenerator::headerFileNameForClass(const AbstractMetaClassCPtr &metaClass)
+{
+ Q_ASSERT(!metaClass->typeEntry()->isSmartPointer());
+ return fileNameForClassHelper(metaClass, u"_wrapper.h"_s);
+}
+
QString ShibokenGenerator::headerFileNameForContext(const GeneratorContext &context)
{
return fileNameForContextHelper(context, u"_wrapper.h"_s);
return funcName;
}
+// Return name of the static function implementing a python override ("sbk_o_..")
+QString ShibokenGenerator::pythonOverrideImplName(const AbstractMetaFunctionCPtr &func)
+{
+ QString result = "sbk_o_"_L1;
+ if (func->isOperatorOverload()) {
+ QString name = pythonOperatorFunctionName(func);
+ name.remove(u'_');
+ result += "op_"_L1 + name;
+ } else {
+ result += func->originalName();
+ }
+ return result;
+}
+
bool ShibokenGenerator::wrapperDiagnostics()
{
return m_options.wrapperDiagnostics;
{
if (type->isWrapperType()) {
QString result = u"Shiboken::Conversions::"_s;
- bool isValue = false;
- if (type->isValue()) {
- const auto cte = std::static_pointer_cast<const ComplexTypeEntry>(type);
- isValue = !cte->isValueTypeWithCopyConstructorOnly();
- }
+ const bool isValue = type->isValue()
+ && std::static_pointer_cast<const CppTypeEntry>(type)->isDefaultConstructible();
result += isValue ? u"isPythonToCppValueConvertible"_s
: u"isPythonToCppPointerConvertible"_s;
result += u"("_s + cpythonTypeNameExt(type) + u", "_s;
s << "Shiboken::GilState &gil, PyObject *" << PYTHON_OVERRIDE_VAR;
argUsed += 2;
}
+
+ if (options.testFlag(PythonOverrideImplementation)) {
+ s << "const char *ownerClassName, const char *funcName, Shiboken::GilState &gil, const Shiboken::AutoDecRef &"
+ << PYTHON_OVERRIDE_VAR;
+ argUsed += 3;
+ }
+
for (const auto &arg : func->arguments()) {
if (options.testFlag(Generator::SkipRemovedArguments) && arg.isModifiedRemoved())
continue;
}
QString ShibokenGenerator::functionSignature(const AbstractMetaFunctionCPtr &func,
- const QString &prepend,
+ const QString &className,
const QString &append,
Options options,
int /* argCount */) const
{
StringStream s(TextStream::Language::Cpp);
// The actual function
- if (!options.testFlag(Option::SkipDefaultValues) && func->isStatic()) // Declaration
+ const bool isDeclaration = !options.testFlag(Option::SkipDefaultValues);
+ const bool isStaticOverride = options.testFlag(Option::PythonOverrideImplementation);
+ if (isDeclaration && (isStaticOverride || func->isStatic()))
s << "static ";
if (func->isEmptyFunction() || func->needsReturnType())
s << functionReturnType(func, options) << ' ';
options |= Generator::SkipReturnType;
// name
- QString name(func->originalName());
+ QString name = isStaticOverride ? pythonOverrideImplName(func) : func->originalName();
if (func->isConstructor())
name = wrapperName(func->ownerClass());
- s << prepend << name << append << '(';
+ if (!isDeclaration && !className.isEmpty())
+ s << className << "::";
+ s << name << append << '(';
writeFunctionArguments(s, func, options);
s << ')';
- if (func->isConstant())
+ if (func->isConstant() && !isStaticOverride)
s << " const";
if (func->exceptionSpecification() == ExceptionSpecification::NoExcept)
if (argCount > 0)
s << ", ";
const bool isVirtualCall = options.testFlag(Option::VirtualCall);
- const bool useStdMove = isVirtualCall && type.isUniquePointer() && type.passByValue();
+ const bool useStdMove = isVirtualCall && type.useStdMove();
s << (useStdMove ? stdMove(argument.name()) : argument.name());
if (!isVirtualCall
void ShibokenGenerator::processClassCodeSnip(QString &code, const GeneratorContext &context) const
{
- auto metaClass = context.metaClass();
+ const auto &metaClass = context.metaClass();
// Replace template variable by the Python Type object
// for the class context in which the variable is used.
code.replace(u"%PYTHONTYPEOBJECT"_s,
AttroCheck result;
if (classNeedsGetattroOverloadFunctionImpl(functionGroups))
result |= AttroCheckFlag::GetattroOverloads;
- if (metaClass->queryFirstFunction(metaClass->functions(),
- FunctionQueryOption::GetAttroFunction)) {
+ if (AbstractMetaClass::queryFirstFunction(metaClass->functions(),
+ FunctionQueryOption::GetAttroFunction)) {
result |= AttroCheckFlag::GetattroUser;
}
if (usePySideExtensions() && metaClass->qualifiedCppName() == qObjectT)
if (std::any_of(funcs.cbegin(), funcs.cend(), isVirtualOverride))
result |= AttroCheckFlag::SetattroMethodOverride;
}
- if (metaClass->queryFirstFunction(metaClass->functions(),
- FunctionQueryOption::SetAttroFunction)) {
+ if (AbstractMetaClass::queryFirstFunction(metaClass->functions(),
+ FunctionQueryOption::SetAttroFunction)) {
result |= AttroCheckFlag::SetattroUser;
}
// PYSIDE-1255: If setattro is generated for a class inheriting
if (func->isAssignmentOperator() || func->isConversionOperator()
|| func->isModifiedRemoved()
|| func->isPrivate() || func->ownerClass() != func->implementingClass()
- || func->isConstructor() || func->isOperatorOverload())
+ || func->isOperatorOverload())
continue;
overloads.append(func);
}
case AbstractMetaFunction::SetAttroFunction:
case AbstractMetaFunction::ArrowOperator: // weird operator overloads
case AbstractMetaFunction::SubscriptOperator:
+ case AbstractMetaFunction::OtherAssignmentOperatorFunction:
return false;
default:
break;
return results;
}
+AbstractMetaFunctionCList
+ ShibokenGenerator::wrapperConstructorsImpl(const AbstractMetaClassCPtr &scope)
+{
+ auto pred = [](const AbstractMetaFunctionCPtr &f) {
+ return ShibokenGenerator::functionGeneration(f).testFlag(FunctionGenerationFlag::WrapperConstructor);
+ };
+ AbstractMetaFunctionCList result;
+ std::copy_if(scope->functions().cbegin(), scope->functions().cend(),
+ std::back_inserter(result), pred);
+ return result;
+}
+
+// Check whether constructors are imported via "using" directive from a base class
+// (simple case of single inheritance)?
+static bool checkConstructorsFromUsingDirective(const AbstractMetaClassCPtr &scope)
+{
+ if (scope->baseClasses().size() != 1)
+ return false;
+ const auto base = scope->baseClass();
+ // base class name == constructors name
+ return scope->isUsingMember(base, base->name(), Access::Public);
+}
+
+// Clone base constructors imported via "using" directive for use in a derived class
+static AbstractMetaFunctionCList
+ cloneUsingConstructors(const AbstractMetaClassCPtr &scope,
+ const AbstractMetaFunctionCList &baseCts)
+{
+ AbstractMetaFunctionCList result;
+ result.reserve(baseCts.size());
+ auto transform = [scope] (const AbstractMetaFunctionCPtr &baseCt) {
+ auto *clone = baseCt->copy();
+ clone->setOriginalName(scope->name());
+ clone->setName(scope->name());
+ clone->setOwnerClass(scope);
+ return AbstractMetaFunctionCPtr(clone);
+ };
+ std::transform(baseCts.cbegin(), baseCts.cend(),
+ std::back_inserter(result), transform);
+ return result;
+}
+
+// Get constructors imported via "using" directive from a base class.
+static AbstractMetaFunctionCList
+ getConstructorsFromUsingDirective(const AbstractMetaClassCPtr &scope)
+{
+ static constexpr auto query = FunctionQueryOption::Constructors
+ | FunctionQueryOption::Visible | FunctionQueryOption::ClassImplements
+ | FunctionQueryOption::NotRemoved;
+ return cloneUsingConstructors(scope, scope->baseClass()->queryFunctions(query));
+}
+
+// Get wrapper constructors imported via "using" directive from a base class
+// (looser criterion).
+AbstractMetaFunctionCList
+ ShibokenGenerator::getWrapperConstructorsFromUsingDirective(const AbstractMetaClassCPtr &scope)
+{
+ AbstractMetaFunctionCList baseCts =
+ scope->baseClass()->queryFunctions(FunctionQueryOption::Constructors);
+ auto pred = [] (const AbstractMetaFunctionCPtr &c) {
+ return !ShibokenGenerator::functionGeneration(c).testFlag(FunctionGenerationFlag::WrapperConstructor);
+ };
+ baseCts.erase(std::remove_if(baseCts.begin(), baseCts.end(), pred), baseCts.end());
+ return cloneUsingConstructors(scope, baseCts);
+}
+
const GeneratorClassInfoCacheEntry &
ShibokenGenerator::getGeneratorClassInfo(const AbstractMetaClassCPtr &scope)
{
if (it == cache->end()) {
it = cache->insert(scope, {});
auto &entry = it.value();
- entry.functionGroups = getFunctionGroupsImpl(scope);
+ entry.functionGroups = getFunctionGroupsImpl(scope, &entry.constructors);
+ const bool useWrapper = shouldGenerateCppWrapper(scope);
+ if (useWrapper)
+ entry.wrapperConstructors = wrapperConstructorsImpl(scope);
+ if (entry.constructors.isEmpty() && checkConstructorsFromUsingDirective(scope)) {
+ entry.constructors.append(getConstructorsFromUsingDirective(scope));
+ entry.wrapperConstructors.append(getWrapperConstructorsFromUsingDirective(scope));
+ }
entry.attroCheck = checkAttroFunctionNeedsImpl(scope, entry.functionGroups);
entry.numberProtocolOperators = getNumberProtocolOperators(scope);
entry.boolCastFunctionO = getBoolCast(scope);
+ if (shouldGenerateCppWrapper(scope)) // Skip final classes
+ entry.reusedOverrides = getReusedOverridesImpl(scope);
}
return it.value();
}
return getGeneratorClassInfo(scope).functionGroups;
}
+AbstractMetaFunctionCList
+ ShibokenGenerator::getConstructors(const AbstractMetaClassCPtr &scope)
+{
+ Q_ASSERT(scope);
+ return getGeneratorClassInfo(scope).constructors;
+}
+
+AbstractMetaFunctionCList ShibokenGenerator::getWrapperConstructors(const AbstractMetaClassCPtr &scope)
+{
+ Q_ASSERT(scope);
+ return getGeneratorClassInfo(scope).wrapperConstructors;
+}
+
+const ShibokenGenerator::FunctionMapping &
+ ShibokenGenerator::getReusedOverridenFunctions(const AbstractMetaClassCPtr &scope)
+{
+ Q_ASSERT(scope);
+ return getGeneratorClassInfo(scope).reusedOverrides;
+}
+
QList<AbstractMetaFunctionCList>
ShibokenGenerator::numberProtocolOperators(const AbstractMetaClassCPtr &scope)
{
}
}
+// For a list of overloads of the same argument count, return a list of functions
+// that can be removed by the type system overload removal rules.
+static AbstractMetaFunctionCList filterFunctions(const OverloadRemovalRules &removalRules,
+ const AbstractMetaFunctionCList &overloads)
+{
+ const auto size = overloads.size();
+ Q_ASSERT(size > 1);
+ AbstractMetaFunctionCList result;
+
+ // Basic parameters that need to be equivalent
+ static constexpr AbstractMetaFunction::CompareResult expected =
+ AbstractMetaFunction::EqualName | AbstractMetaFunction::EqualVirtual
+ | AbstractMetaFunction::EqualConst | AbstractMetaFunction::EqualStatic
+ | AbstractMetaFunction::EqualReturnType;
+
+
+ // Find the varying argument and check if otherwise equivalent
+ AbstractMetaFunction::CompareResult differingArgMask{};
+ for (qsizetype a = 1; a < size; ++a) {
+ auto cr = overloads.constFirst()->compareTo(overloads.at(a).get());
+ if ((cr & expected) != expected)
+ return result;
+ auto argMask = cr & AbstractMetaFunction::Differ4ArgumentsMask;
+ if (a == 1)
+ differingArgMask = argMask;
+ else if (differingArgMask != argMask)
+ return result;
+ }
+
+ // Turn bit mask into argument number and check if only one argument differs
+ qsizetype argNo = -1;
+ if (differingArgMask == AbstractMetaFunction::DifferArgument1)
+ argNo = 0;
+ else if (differingArgMask == AbstractMetaFunction::DifferArgument2)
+ argNo = 1;
+ else if (differingArgMask == AbstractMetaFunction::DifferArgument3)
+ argNo = 2;
+ else if (differingArgMask == AbstractMetaFunction::DifferArgument4)
+ argNo = 3;
+ if (argNo < 0) // Several arguments differ
+ return result;
+
+ // Retrieve list of types of the varying argument
+ // FIXME PYSIDE-7: Refactor using C++ 20 views
+ QStringList types;
+ types.reserve(size);
+ for (const auto &f : overloads) {
+ auto amt = f->arguments().at(argNo).type();
+ if (!amt.passByValue() && !amt.passByConstRef()) // Only simple types so far
+ return result;
+ types.append(amt.basicPrimitiveName());
+ }
+
+ // Apply rules and compile list of redundant functions
+ for (const auto &rule : removalRules) {
+ if (const auto index = types.indexOf(rule.type); index != -1) {
+ for (const auto &redundantType : rule.redundantTypes) {
+ if (const auto index2 = types.indexOf(redundantType); index2 != -1) {
+ auto redundant = overloads.at(index2);
+ if (!result.contains(redundant)) { // nested long->int->short rule?
+ ReportHandler::addGeneralMessage(msgRemoveRedundantOverload(redundant, rule.type));
+ result.append(redundant);
+ }
+ }
+ }
+ }
+ }
+ return result;
+}
+
+static bool argCountLessThan(const AbstractMetaFunctionCPtr &f1, const AbstractMetaFunctionCPtr &f2)
+{
+ return f1->arguments().size() < f2->arguments().size();
+}
+
+// For a list of overloads of the same name, remove functions with redundant arguments.
+// as defined by the type system overload removal rules. It is important that the order
+// is preserved, else topological sorting in OverloadData will go haywire.
+static void filterAllFunctions(const OverloadRemovalRules &removalRules,
+ AbstractMetaFunctionCList *overloads)
+{
+ if (overloads->size() < 2)
+ return;
+ const auto maxArgsIt = std::max_element(overloads->cbegin(), overloads->cend(), argCountLessThan);
+ const auto maxArgs = (*maxArgsIt)->arguments().size();
+ if (maxArgs == 0)
+ return;
+
+ // FIXME PYSIDE-7: Refactor using C++ 20 views
+ for (qsizetype ac = 0; ac <= maxArgs; ++ac) {
+ // Check on lists of the same argument count.
+ AbstractMetaFunctionCList list;
+ auto sameArgumentCount = [ac](const AbstractMetaFunctionCPtr &f) {
+ return f->arguments().size() == ac; };
+ std::copy_if(overloads->cbegin(), overloads->cend(), std::back_inserter(list),
+ sameArgumentCount);
+ if (list.size() > 1) {
+ const auto redundant = filterFunctions(removalRules, list);
+ for (const auto &r : redundant)
+ overloads->removeAll(r);
+ }
+ }
+}
+
ShibokenGenerator::FunctionGroups
- ShibokenGenerator::getFunctionGroupsImpl(const AbstractMetaClassCPtr &scope)
+ ShibokenGenerator::getFunctionGroupsImpl(const AbstractMetaClassCPtr &scope,
+ AbstractMetaFunctionCList *constructors)
{
AbstractMetaFunctionCList lst = scope->functions();
scope->getFunctionsFromInvisibleNamespacesToBeGenerated(&lst);
+ const OverloadRemovalRules &removalRules = TypeDatabase::instance()->overloadRemovalRules();
FunctionGroups results;
- for (const auto &func : lst) {
+ for (const auto &func : std::as_const(lst)) {
if (isGroupable(func)
&& func->ownerClass() == func->implementingClass()
&& func->generateBinding()) {
+ if (func->isConstructor()) {
+ if (func->functionType() != AbstractMetaFunction::MoveConstructorFunction)
+ constructors->append(func);
+ continue;
+ }
auto it = results.find(func->name());
if (it == results.end()) {
it = results.insert(func->name(), AbstractMetaFunctionCList(1, func));
}
getInheritedOverloads(scope, &it.value());
removeConstOverloads(&it.value());
+ if (!removalRules.isEmpty())
+ filterAllFunctions(removalRules, &it.value());
}
}
return results;
}
+ShibokenGenerator::FunctionMapping
+ ShibokenGenerator::getReusedOverridesImpl(const AbstractMetaClassCPtr &metaClass)
+{
+ ShibokenGenerator::FunctionMapping result;
+ for (const auto &func : metaClass->functions()) {
+ const auto generation = functionGeneration(func);
+ if (generation.testFlag(FunctionGenerationFlag::VirtualMethod)) {
+ OverrideCacheEntry fce(func);
+ auto it = pythonOverrideCache.constFind(fce);
+ if (it == pythonOverrideCache.cend())
+ pythonOverrideCache.insert(fce);
+ else
+ result.insert(func, it->function());
+ }
+ }
+ return result;
+}
+
static bool removeNumberProtocolOperator(const AbstractMetaFunctionCPtr &f)
{
return !f->generateBinding()
#include "typesystem_typedefs.h"
#include "typesystem_enums.h"
-#include <QtCore/QRegularExpression>
+#include <QtCore/qhash.h>
+#include <QtCore/qregularexpression.h>
#include <array>
#include <optional>
};
Q_DECLARE_FLAGS(AttroCheck, AttroCheckFlag);
+ using FunctionMapping = QHash<AbstractMetaFunctionCPtr, AbstractMetaFunctionCPtr>;
using FunctionGroups = QMap<QString, AbstractMetaFunctionCList>; // Sorted
ShibokenGenerator();
*/
FunctionGroups getGlobalFunctionGroups() const;
static FunctionGroups getFunctionGroups(const AbstractMetaClassCPtr &scope);
+ /// Returns the constructors for which bindings should be generated.
+ /// \param scope Where to search for functions
+ static AbstractMetaFunctionCList getConstructors(const AbstractMetaClassCPtr &scope);
+ /// Returns the constructors which should be added to wrapper class.
+ /// \param scope Where to search for functions
+ static AbstractMetaFunctionCList getWrapperConstructors(const AbstractMetaClassCPtr &scope);
+ /// Returns mapping of virtual functions whose override handling code can be re-used.
+ static const FunctionMapping &
+ getReusedOverridenFunctions(const AbstractMetaClassCPtr &scope);
static QList<AbstractMetaFunctionCList>
numberProtocolOperators(const AbstractMetaClassCPtr &scope);
* \param arg_count the number of function arguments
*/
QString functionSignature(const AbstractMetaFunctionCPtr &func,
- const QString &prepend = QString(),
- const QString &append = QString(),
+ const QString &className = {},
+ const QString &append = {},
Options options = NoOption,
int arg_count = -1) const;
static QString fullPythonClassName(const AbstractMetaClassCPtr &metaClass);
+ static QString headerFileNameForClass(const AbstractMetaClassCPtr &metaClass);
static QString headerFileNameForContext(const GeneratorContext &context);
IncludeGroup baseWrapperIncludes(const GeneratorContext &classContext) const;
static QString fullPythonFunctionName(const AbstractMetaFunctionCPtr &func, bool forceFunc);
+ static QString pythonOverrideImplName(const AbstractMetaFunctionCPtr &func);
static bool wrapperDiagnostics();
static const GeneratorClassInfoCacheEntry &
getGeneratorClassInfo(const AbstractMetaClassCPtr &scope);
- static FunctionGroups getFunctionGroupsImpl(const AbstractMetaClassCPtr &scope);
+ static FunctionGroups getFunctionGroupsImpl(const AbstractMetaClassCPtr &scope,
+ AbstractMetaFunctionCList *constructors);
+ static AbstractMetaFunctionCList wrapperConstructorsImpl(const AbstractMetaClassCPtr &scope);
+ static AbstractMetaFunctionCList getWrapperConstructorsFromUsingDirective(const AbstractMetaClassCPtr &scope);
static QList<AbstractMetaFunctionCList>
getNumberProtocolOperators(const AbstractMetaClassCPtr &metaClass);
static BoolCastFunctionOptional getBoolCast(const AbstractMetaClassCPtr &metaClass);
static bool classNeedsGetattroOverloadFunctionImpl(const FunctionGroups &functionGroups);
static AttroCheck checkAttroFunctionNeedsImpl(const AbstractMetaClassCPtr &metaClass,
const FunctionGroups &functionGroups);
+ static FunctionMapping getReusedOverridesImpl(const AbstractMetaClassCPtr &metaClass);
static bool isVirtualOverride(const AbstractMetaFunctionCPtr &f);
QString translateTypeForWrapperMethod(const AbstractMetaType &cType,
QString &code) const;
/// Replaces the %CONVERTTOPYTHON type system variable.
- inline void replaceConvertToPythonTypeSystemVariable(QString &code) const
+ void replaceConvertToPythonTypeSystemVariable(QString &code) const
{
replaceConverterTypeSystemVariable(TypeSystemToPythonFunction, code);
}
/// Replaces the %CONVERTTOCPP type system variable.
- inline void replaceConvertToCppTypeSystemVariable(QString &code) const
+ void replaceConvertToCppTypeSystemVariable(QString &code) const
{
replaceConverterTypeSystemVariable(TypeSystemToCppFunction, code);
}
/// Replaces the %ISCONVERTIBLE type system variable.
- inline void replaceIsConvertibleToCppTypeSystemVariable(QString &code) const
+ void replaceIsConvertibleToCppTypeSystemVariable(QString &code) const
{
replaceConverterTypeSystemVariable(TypeSystemIsConvertibleFunction, code);
}
/// Replaces the %CHECKTYPE type system variable.
- inline void replaceTypeCheckTypeSystemVariable(QString &code) const
+ void replaceTypeCheckTypeSystemVariable(QString &code) const
{
replaceConverterTypeSystemVariable(TypeSystemCheckFunction, code);
}
pep384impl.cpp pep384impl.h
pyobjectholder.h
sbkarrayconverter.cpp sbkarrayconverter.h sbkarrayconverter_p.h
+sbkbindingutils.cpp sbkbindingutils.h
sbkcontainer.cpp sbkcontainer.h
sbkconverter.cpp sbkconverter.h sbkconverter_p.h
sbkcppstring.cpp sbkcppstring.h sbkcpptonumpy.h
shiboken_compute_python_libraries()
if(PYTHON_LIMITED_API)
- target_compile_definitions(libshiboken PUBLIC "-DPy_LIMITED_API=0x03080000")
+ target_compile_definitions(libshiboken PUBLIC "-DPy_LIMITED_API=0x03090000")
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
helper.h
pyobjectholder.h
sbkarrayconverter.h
+ sbkbindingutils.h
sbkcontainer.h
sbkconverter.h
sbkcpptonumpy.h
/// Returns the pointer of the Python object being held.
[[nodiscard]] PyObject *object() const { return m_pyObj; }
[[nodiscard]] operator PyObject *() const { return m_pyObj; }
-#ifndef Py_LIMITED_API
- [[deprecated]] inline operator PyTupleObject *()
- { return reinterpret_cast<PyTupleObject *>(m_pyObj); }
-#endif
- inline operator bool() const { return m_pyObj != nullptr; }
- inline PyObject *operator->() { return m_pyObj; }
+ operator bool() const { return m_pyObj != nullptr; }
+ PyObject *operator->() { return m_pyObj; }
template<typename T>
[[deprecated]] T cast()
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include "autodecref.h"
#include "basewrapper.h"
#include "basewrapper_p.h"
#include "bindingmanager.h"
+#include "gilstate.h"
#include "helper.h"
#include "pep384ext.h"
#include "sbkconverter.h"
#include "sbkerrors.h"
#include "sbkfeature_base.h"
-#include "sbkstring.h"
#include "sbkstaticstrings.h"
#include "sbkstaticstrings_p.h"
+#include "sbkstring.h"
#include "sbktypefactory.h"
-#include "autodecref.h"
-#include "gilstate.h"
-#include <string>
-#include <cstring>
-#include <cstddef>
-#include <set>
-#include <sstream>
-#include <algorithm>
-#include "threadstatesaver.h"
#include "signature.h"
#include "signature_p.h"
+#include "threadstatesaver.h"
#include "voidptr.h"
-#include <string>
+#include <algorithm>
+#include <cstddef>
+#include <cstring>
#include <iostream>
+#include <set>
#include <sstream>
+#include <string>
#if defined(__APPLE__)
#include <dlfcn.h>
// Need to decref the type if this is the dealloc func; if type
// is subclassed, that dealloc func will decref (see subtype_dealloc
// in typeobject.c in the python sources)
- auto dealloc = PyType_GetSlot(pyType, Py_tp_dealloc);
+ auto *dealloc = PyType_GetSlot(pyType, Py_tp_dealloc);
// PYSIDE-939: Additional rule: Also when a subtype is heap allocated,
// then the subtype_dealloc deref will be suppressed, and we need again
void SbkObjectType_tp_dealloc(PyTypeObject *sbkType)
{
SbkObjectTypePrivate *sotp = PepType_SOTP(sbkType);
- auto pyObj = reinterpret_cast<PyObject *>(sbkType);
+ auto *pyObj = reinterpret_cast<PyObject *>(sbkType);
PyObject_GC_UnTrack(pyObj);
#if !defined(Py_LIMITED_API) && !defined(PYPY_VERSION)
auto *self = reinterpret_cast<SbkObject *>(obSelf);
Py_INCREF(obSubtype);
- auto d = new SbkObjectPrivate;
+ auto *d = new SbkObjectPrivate;
auto *sotp = PepType_SOTP(sbkSubtype);
int numBases = ((sotp && sotp->is_multicpp) ?
return Py_TYPE(type) == meta || PyType_IsSubtype(Py_TYPE(type), meta);
}
+// Global functions from folding.
+
+// The common end.
+PyObject *Sbk_ReturnFromPython_None()
+{
+ if (Shiboken::Errors::occurred() != nullptr) {
+ return {};
+ }
+ Py_RETURN_NONE;
+}
+
+PyObject *Sbk_ReturnFromPython_Result(PyObject *pyResult)
+{
+ if (Shiboken::Errors::occurred() != nullptr || pyResult == nullptr) {
+ Py_XDECREF(pyResult);
+ return {};
+ }
+ return pyResult;
+}
+
+PyObject *Sbk_ReturnFromPython_Self(PyObject *self)
+{
+ if (Shiboken::Errors::occurred() != nullptr) {
+ return {};
+ }
+ Py_INCREF(self);
+ return self;
+}
+
+// The virtual function call
+PyObject *Sbk_GetPyOverride(const void *voidThis, Shiboken::GilState &gil, const char *funcName,
+ bool *resultCache, PyObject **nameCache)
+{
+ PyObject *pyOverride{};
+ if (!*resultCache) {
+ gil.acquire();
+ pyOverride = Shiboken::BindingManager::instance().getOverride(voidThis, nameCache, funcName);
+ if (pyOverride == nullptr) {
+ *resultCache = true;
+ gil.release();
+ } else if (Shiboken::Errors::occurred() != nullptr) {
+ // Give up.
+ Py_XDECREF(pyOverride);
+ pyOverride = nullptr;
+ }
+ }
+ return pyOverride;
+}
+
} //extern "C"
setDestructorFunction(type, cppObjDtor);
auto *ob_type = reinterpret_cast<PyObject *>(type);
+ if (wrapperFlags & InternalWrapper) {
+ // Type wraps another wrapper class and isn't part of any module. This is used to extend
+ // Qt types with additional functionality, but from within PySide itself. In order for
+ // the re-wrapped class to call the autogenerated __init__ method, is_user_type must be set
+ // to 1. Otherwise the Shiboken::ObjectType::canCallConstructor test will fail.
+ // This is currently only used by QtRemoteObjects to create types dynamically.
+ sotp->is_user_type = 1;
+ return type;
+ }
+
if (wrapperFlags & InnerClass) {
// PYSIDE-2230: Instead of tp_dict, use the enclosing type.
// This stays interface compatible.
namespace Object
{
-static void recursive_invalidate(SbkObject *self, std::set<SbkObject *>& seen);
+static void recursive_invalidate(SbkObject *self, std::set<SbkObject *> &seen);
bool checkType(PyObject *pyObj)
{
}
/* Needed forward declarations */
-static void recursive_invalidate(PyObject *pyobj, std::set<SbkObject *>& seen);
-static void recursive_invalidate(SbkObject *self, std::set<SbkObject *> &seen);
+static void recursive_invalidate(PyObject *pyobj, std::set<SbkObject *> &seen);
void invalidate(PyObject *pyobj)
{
return true;
}
- auto priv = reinterpret_cast<SbkObject *>(pyObj)->d;
+ auto *priv = reinterpret_cast<SbkObject *>(pyObj)->d;
if (!priv->cppObjectCreated && isUserType(pyObj)) {
PyErr_Format(PyExc_RuntimeError, "'__init__' method of object's base class (%s) not called.",
}
bool parentIsNull = !parent || parent == Py_None;
- auto parent_ = reinterpret_cast<SbkObject *>(parent);
- auto child_ = reinterpret_cast<SbkObject *>(child);
+ auto *parent_ = reinterpret_cast<SbkObject *>(parent);
+ auto *child_ = reinterpret_cast<SbkObject *>(child);
if (!parentIsNull) {
if (!parent_->d->parentInfo)
#include "sbkpython.h"
#include "shibokenmacros.h"
#include "sbkmodule.h"
+#include "gilstate.h"
#include <vector>
#include <string>
/// PYSIDE-2230: Check if an object is an SbkObject.
LIBSHIBOKEN_API bool SbkObjectType_Check(PyTypeObject *type);
+/// PYSIDE-2701: Some improvements from folding optimizations.
+LIBSHIBOKEN_API PyObject *Sbk_ReturnFromPython_None();
+LIBSHIBOKEN_API PyObject *Sbk_ReturnFromPython_Result(PyObject *pyResult);
+LIBSHIBOKEN_API PyObject *Sbk_ReturnFromPython_Self(PyObject *self);
+LIBSHIBOKEN_API PyObject *Sbk_GetPyOverride(const void *voidThis, Shiboken::GilState &gil,
+ const char *funcName, bool *resultCache,
+ PyObject **nameCache);
} // extern "C"
namespace Shiboken
{
InnerClass = 0x1,
DeleteInMainThread = 0x2,
- Value = 0x4
+ Value = 0x4,
+ InternalWrapper = 0x8
};
/**
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include "bindingmanager.h"
+
#include "autodecref.h"
#include "basewrapper.h"
#include "basewrapper_p.h"
-#include "bindingmanager.h"
-#include "gilstate.h"
#include "helper.h"
+#include "sbkfeature_base.h"
#include "sbkmodule.h"
-#include "sbkstring.h"
#include "sbkstaticstrings.h"
-#include "sbkfeature_base.h"
-#include "debugfreehook.h"
+#include "sbkstring.h"
#include <cstddef>
#include <cstring>
auto *d = PepType_SOTP(sbkType);
int numBases = ((d && d->is_multicpp) ? getNumberOfCppBaseClasses(Py_TYPE(sbkObj)) : 1);
- void ** cptrs = reinterpret_cast<SbkObject *>(sbkObj)->d->cptr;
+ void **cptrs = sbkObj->d->cptr;
const int *mi_offsets = d != nullptr ? d->mi_offsets : nullptr;
for (int i = 0; i < numBases; ++i) {
if (cptrs[i] != nullptr)
Py_DECREF(method);
method = nullptr;
}
- } else if (PyObject_HasAttr(method, PyName::im_self())
- && PyObject_HasAttr(method, PyName::im_func())
- && PyObject_HasAttr(method, Shiboken::PyMagicName::code())) {
+ } else if (isCompiledMethod(method)) {
PyObject *im_self = PyObject_GetAttr(method, PyName::im_self());
// Not retaining a reference inline with what PyMethod_GET_SELF does.
Py_DECREF(im_self);
if (method != nullptr) {
PyObject *mro = Py_TYPE(wrapper)->tp_mro;
- int size = PyTuple_Size(mro);
bool defaultFound = false;
// The first class in the mro (index 0) is the class being checked and it should not be tested.
// The last class in the mro (size - 1) is the base Python object class which should not be tested also.
- for (int idx = 1; idx < size - 1; ++idx) {
+ for (Py_ssize_t idx = 1, size = PyTuple_Size(mro); idx < size - 1; ++idx) {
auto *parent = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(mro, idx));
AutoDecRef parentDict(PepType_GetDict(parent));
if (parentDict) {
std::cerr << "-------------------------------\n"
<< "WrapperMap size: " << wrapperMap.size() << " Types: "
<< m_d->classHierarchy.nodeSet().size() << '\n';
- for (auto it = wrapperMap.begin(), end = wrapperMap.end(); it != end; ++it) {
- const SbkObject *sbkObj = it->second;
- std::cerr << "key: " << it->first << ", value: "
+ for (auto it : wrapperMap) {
+ const SbkObject *sbkObj = it.second;
+ std::cerr << "key: " << it.first << ", value: "
<< static_cast<const void *>(sbkObj) << " ("
<< (Py_TYPE(sbkObj))->tp_name << ", refcnt: "
<< Py_REFCNT(reinterpret_cast<const PyObject *>(sbkObj)) << ")\n";
"""
import sys
-import os
import argparse
-import pickle
from textwrap import dedent
-from pathlib import path
+from pathlib import Path
def source_archive(module, modname):
# modname = module.__name__
# Do not use: Some modules rename themselves!
version = ".".join(map(str, sys.version_info[:3]))
- shortname = fname.stem
preamble = dedent(fr"""
# BEGIN SOURCE ARCHIVE Python {version} module {modname}
print("modules:", args.modules)
ret = license_header() + read_all(args.modules)
ma_mi = "_".join(map(str, sys.version_info[:2]))
- outpath = Path(__file__).parents[2] / Path("shibokenmodule",
- "files.dir", "shibokensupport", f"python_minilib_{ma_mi}.py")
+ outpath = Path(__file__).parents[2] / Path("shibokenmodule", "files.dir",
+ "shibokensupport", f"python_minilib_{ma_mi}.py")
with outpath.open("w") as f:
f.write(ret)
imports were in the functions. Moved them outside into the globals.
"""
-recursion_trap = 0
-
import base64
-import importlib
import io
import os
import sys
from pathlib import Path
+recursion_trap = 0
+
+
def bootstrap():
global recursion_trap
for key in list(key for key in sys.modules if key.startswith(prefix)):
del sys.modules[key]
try:
- import shibokensupport
+ import shibokensupport # noqa: F401
yield
except Exception as e:
f = sys.stderr
from shibokensupport.signature import loader
return loader
-# Newer functionality:
-# This function checks if the support directory exist and returns it.
-# If does not exist, we try to create it and return it.
-# Otherwise, we return None.
def find_incarnated_files():
+ """Newer functionality: This function checks if the support directory exist and
+ returns it. If does not exist, we try to create it and return it. Otherwise,
+ we return None."""
import shiboken6 as root
files_dir = Path(root.__file__).resolve().parent / "files.dir"
handle_embedding_switch(files_dir)
# But that has the side-effect that we need to delay the feature
# initialization until all function pointers are set.
# See `post_init_func` in signature_globals.cpp .
- import shibokensupport.signature.loader
+ import shibokensupport.signature.loader # noqa: F401
del sys.path[0]
return files_dir
return None
try:
# First check mkdir to get an error when we cannot write.
files_dir.mkdir(exist_ok=True)
- except os.error as e:
+ except os.error:
print(f"SBK_EMBED=False: Warning: Cannot write into {files_dir}")
return None
try:
# a temporary zip file.
# PYSIDE-1621: make zip file access totally virtual
+
def prepare_zipfile():
"""
Old approach:
"""
# 'zipstring_sequence' comes from signature.cpp
- zipbytes = base64.b64decode(''.join(zipstring_sequence))
+ zipbytes = base64.b64decode(''.join(zipstring_sequence)) # noqa: F821
vzip = zipfile.ZipFile(io.BytesIO(zipbytes))
return sys.meta_path, EmbeddableZipImporter(vzip)
return None
self.zfile = zip_file
- self._mod2path = {p2m(_.filename) : _.filename for _ in zip_file.filelist}
+ self._mod2path = {p2m(_.filename): _.filename for _ in zip_file.filelist}
def find_spec(self, fullname, path, target=None):
path = self._mod2path.get(fullname)
namespace Shiboken
{
-GilState::GilState()
+GilState::GilState(bool acquire)
{
- if (Py_IsInitialized()) {
+ if (acquire && Py_IsInitialized()) {
m_gstate = PyGILState_Ensure();
m_locked = true;
}
release();
}
+void GilState::acquire()
+{
+ if (Py_IsInitialized()) {
+ m_gstate = PyGILState_Ensure();
+ m_locked = true;
+ }
+}
+
void GilState::release()
{
if (m_locked && Py_IsInitialized()) {
GilState &operator=(const GilState &) = delete;
GilState &operator=(GilState &&) = delete;
- GilState();
+ explicit GilState(bool acquire=true);
~GilState();
+ void acquire();
void release();
void abandon();
private:
#include "basewrapper_p.h"
#include "sbkstring.h"
#include "sbkstaticstrings.h"
-#include "sbkstaticstrings.h"
#include "pep384impl.h"
#include <algorithm>
#include <optional>
-
#include <iomanip>
#include <iostream>
#include <climits>
static void formatPyDict(PyObject *obj, std::ostream &str)
{
- PyObject *key;
- PyObject *value;
+ PyObject *key{};
+ PyObject *value{};
Py_ssize_t pos = 0;
str << '{';
while (PyDict_Next(obj, &pos, &key, &value) != 0) {
}
#endif // _WIN32
+static char *strDup(const char *s) // strdup() using new[] for symmetry with the Windows code
+{
+ auto len = std::strlen(s);
+ auto *result = new char[1 + len];
+ // copy len-characters to avoid if we have a null terminator in the middle.
+ std::memcpy(result, s, len);
+ result[len] = '\0';
+ return result;
+}
+
// PySide-510: Changed from PySequence to PyList, which is correct.
-bool listToArgcArgv(PyObject *argList, int *argc, char ***argv, const char *defaultAppName)
+bool listToArgcArgv(PyObject *argList, int *argcIn, char ***argvIn, const char *defaultAppName)
{
+ *argcIn = 0;
+ *argvIn = nullptr;
if (!PyList_Check(argList))
return false;
- if (!defaultAppName)
- defaultAppName = "PySideApplication";
-
- // Check all items
Shiboken::AutoDecRef args(PySequence_Fast(argList, nullptr));
- Py_ssize_t numArgs = PySequence_Size(argList);
- for (Py_ssize_t i = 0; i < numArgs; ++i) {
- PyObject *item = PyList_GET_ITEM(args.object(), i);
- if (!PyBytes_Check(item) && !PyUnicode_Check(item))
- return false;
+ const Py_ssize_t numArgs = PySequence_Size(argList);
+ if (numArgs == 0) { // Try to get the script name
+ auto *argv = new char *[1];
+ *argvIn = argv;
+ *argcIn = 1;
+ if (PyObject *appName = PyDict_GetItem(PyEval_GetGlobals(), Shiboken::PyMagicName::file()))
+ argv[0] = strDup(Shiboken::String::toCString(appName));
+ else
+ argv[0] = strDup(defaultAppName ? defaultAppName : "PySideApplication");
+ return true;
}
- bool hasEmptyArgList = numArgs == 0;
- if (hasEmptyArgList)
- numArgs = 1;
-
- *argc = numArgs;
- *argv = new char *[*argc];
+ auto *argv = new char *[numArgs];
+ std::fill(argv, argv + numArgs, nullptr);
- if (hasEmptyArgList) {
- // Try to get the script name
- PyObject *globals = PyEval_GetGlobals();
- PyObject *appName = PyDict_GetItem(globals, Shiboken::PyMagicName::file());
- (*argv)[0] = strdup(appName ? Shiboken::String::toCString(appName) : defaultAppName);
- } else {
- for (int i = 0; i < numArgs; ++i) {
- PyObject *item = PyList_GET_ITEM(args.object(), i);
- char *string = nullptr;
- if (Shiboken::String::check(item)) {
+ for (Py_ssize_t i = 0; i < numArgs; ++i) {
+ PyObject *item = PyList_GetItem(args.object(), i);
+ if (Shiboken::String::check(item)) {
#ifdef _WIN32
- string = toWindowsConsoleEncoding(item);
+ argv[i] = toWindowsConsoleEncoding(item);
#else
- string = strdup(Shiboken::String::toCString(item));
+ argv[i] = strDup(Shiboken::String::toCString(item));
#endif
- }
- (*argv)[i] = string;
+ } else {
+ deleteArgv(int(i), argv);
+ return false;
}
}
+ *argcIn = int(numArgs);
+ *argvIn = argv;
return true;
}
+void deleteArgv(int argc, char **argv)
+{
+ for (int a = 0; a < argc; ++a)
+ delete [] argv[a];
+ delete [] argv;
+}
+
int *sequenceToIntArray(PyObject *obj, bool zeroTerminated)
{
AutoDecRef seq(PySequence_Fast(obj, "Sequence of ints expected"));
Py_ssize_t size = PySequence_Size(seq.object());
int *array = new int[size + (zeroTerminated ? 1 : 0)];
- for (int i = 0; i < size; i++) {
+ for (Py_ssize_t i = 0; i < size; i++) {
Shiboken::AutoDecRef item(PySequence_GetItem(seq.object(), i));
if (!PyLong_Check(item)) {
PyErr_SetString(PyExc_TypeError, "Sequence of ints expected");
// check the necessary memory
int size = vsnprintf(nullptr, 0, format, args) + 1;
- auto message = new char[size];
+ auto *message = new char[size];
int result = 0;
if (message) {
// format the message
return result;
}
+bool isCompiledMethod(PyObject *method)
+{
+ return method != nullptr && method != Py_None
+ && PyObject_HasAttr(method, PyName::im_self()) != 0
+ && PyObject_HasAttr(method, PyName::im_func()) != 0
+ && PyObject_HasAttr(method, Shiboken::PyMagicName::code()) != 0;
+}
+
#if !defined(Py_LIMITED_API) && PY_VERSION_HEX >= 0x030A0000 && !defined(PYPY_VERSION)
static int _getPyVerbose()
{
*/
LIBSHIBOKEN_API bool listToArgcArgv(PyObject *argList, int *argc, char ***argv, const char *defaultAppName = nullptr);
+/// Delete a a list of arguments created by listToArgcArgv()
+LIBSHIBOKEN_API void deleteArgv(int argc, char **argv);
+
/**
* Convert a python sequence into a heap-allocated array of ints.
*
/// \returns Fixed name (allocated).
LIBSHIBOKEN_API const char *typeNameOf(const char *typeIdName);
+/// Returns whether \a method is a compiled method (Nuitka).
+LIBSHIBOKEN_API bool isCompiledMethod(PyObject *method);
+
/**
* Creates and automatically deallocates C++ arrays.
*/
template<class T>
-class AutoArrayPointer
+class ArrayPointer
{
public:
- AutoArrayPointer(const AutoArrayPointer &) = delete;
- AutoArrayPointer(AutoArrayPointer &&) = delete;
- AutoArrayPointer &operator=(const AutoArrayPointer &) = delete;
- AutoArrayPointer &operator=(AutoArrayPointer &&) = delete;
+ ArrayPointer(const ArrayPointer &) = delete;
+ ArrayPointer(ArrayPointer &&) = delete;
+ ArrayPointer &operator=(const ArrayPointer &) = delete;
+ ArrayPointer &operator=(ArrayPointer &&) = delete;
-
- explicit AutoArrayPointer(Py_ssize_t size) { data = new T[size]; }
+ explicit ArrayPointer(Py_ssize_t size) : data(new T[size]) {}
T &operator[](Py_ssize_t pos) { return data[pos]; }
operator T *() const { return data; }
- ~AutoArrayPointer() { delete[] data; }
+ ~ArrayPointer() { delete[] data; }
private:
T *data;
};
+template <class T>
+using AutoArrayPointer = ArrayPointer<T>; // deprecated
+
using ThreadId = unsigned long long;
LIBSHIBOKEN_API ThreadId currentThreadId();
LIBSHIBOKEN_API ThreadId mainThreadId();
* Newly introduced convenience functions
*
*/
-#ifdef Py_LIMITED_API
-
-PyObject *
-PyImport_GetModule(PyObject *name)
-{
- PyObject *modules = PyImport_GetModuleDict();
- if (modules == nullptr) {
- PyErr_SetString(PyExc_RuntimeError, "unable to get sys.modules");
- return nullptr;
- }
- Py_INCREF(modules);
- PyObject *m{};
- if (PyDict_CheckExact(modules)) {
- m = PyDict_GetItemWithError(modules, name); /* borrowed */
- Py_XINCREF(m);
- } else {
- m = PyObject_GetItem(modules, name);
- if (m == nullptr && PyErr_ExceptionMatches(PyExc_KeyError))
- PyErr_Clear();
- }
- Py_DECREF(modules);
- return m;
-}
-
-#endif // Py_LIMITED_API
// 2020-06-16: For simplicity of creating arbitrary things, this function
// is now made public.
#define _PepUnicode_IS_ASCII PyUnicode_IS_ASCII
#endif
-/*****************************************************************************
- *
- * RESOLVED: bytesobject.h
- *
- */
-#ifdef Py_LIMITED_API
-#define PyBytes_AS_STRING(op) PyBytes_AsString(op)
-#define PyBytes_GET_SIZE(op) PyBytes_Size(op)
-#endif
-
-/*****************************************************************************
- *
- * RESOLVED: floatobject.h
- *
- */
-#ifdef Py_LIMITED_API
-#define PyFloat_AS_DOUBLE(op) PyFloat_AsDouble(op)
-#endif
-
-/*****************************************************************************
- *
- * RESOLVED: tupleobject.h
- *
- */
-#ifdef Py_LIMITED_API
-#define PyTuple_GET_ITEM(op, i) PyTuple_GetItem((PyObject *)op, i)
-#define PyTuple_SET_ITEM(op, i, v) PyTuple_SetItem(op, i, v)
-#define PyTuple_GET_SIZE(op) PyTuple_Size((PyObject *)op)
-#endif
-
-/*****************************************************************************
- *
- * RESOLVED: listobject.h
- *
- */
-#ifdef Py_LIMITED_API
-#define PyList_GET_ITEM(op, i) PyList_GetItem(op, i)
-#define PyList_SET_ITEM(op, i, v) PyList_SetItem(op, i, v)
-#define PyList_GET_SIZE(op) PyList_Size(op)
-#endif
-
/*****************************************************************************
*
* RESOLVED: methodobject.h
#ifdef Py_LIMITED_API
using PyCFunctionObject = struct _pycfunc;
-#define PyCFunction_GET_FUNCTION(func) PyCFunction_GetFunction((PyObject *)func)
-#define PyCFunction_GET_SELF(func) PyCFunction_GetSelf((PyObject *)func)
-#define PyCFunction_GET_FLAGS(func) PyCFunction_GetFlags((PyObject *)func)
#define PepCFunction_GET_NAMESTR(func) \
_PepUnicode_AsString(PyObject_GetAttrString((PyObject *)func, "__name__"))
#else
-#define PepCFunction_GET_NAMESTR(func) ((func)->m_ml->ml_name)
+#define PepCFunction_GET_NAMESTR(func) \
+ (reinterpret_cast<const PyCFunctionObject *>(func)->m_ml->ml_name)
#endif
/*****************************************************************************
*
* This is not defined if Py_LIMITED_API is defined.
*/
-#ifdef Py_LIMITED_API
-LIBSHIBOKEN_API PyObject *PyImport_GetModule(PyObject *name);
-#endif // Py_LIMITED_API
// Evaluate a script and return the variable `result`
LIBSHIBOKEN_API PyObject *PepRun_GetResult(const char *command);
#include "sbkarrayconverter_p.h"
#include "helper.h"
#include "sbkconverter.h"
-#include "sbkconverter_p.h"
#include <longobject.h>
#include <floatobject.h>
--- /dev/null
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "sbkbindingutils.h"
+
+#include "autodecref.h"
+
+#include <algorithm>
+
+namespace Shiboken {
+
+static const ArgumentNameIndexMapping *
+ findMapping(const ArgumentNameIndexMapping *i1,
+ const ArgumentNameIndexMapping *i2, PyObject *needle)
+{
+ return std::find_if(i1, i2, [needle](const ArgumentNameIndexMapping &m) {
+ return PyUnicode_CompareWithASCIIString(needle, m.name) == 0;
+ });
+}
+
+bool parseKeywordArguments(PyObject *kwds,
+ const ArgumentNameIndexMapping *mapping, size_t size,
+ Shiboken::AutoDecRef &errInfo, PyObject **pyArgs)
+{
+ if (kwds == nullptr || PyDict_Size(kwds) == 0)
+ return true;
+ PyObject *key{};
+ PyObject *value{};
+ Py_ssize_t pos = 0;
+ const ArgumentNameIndexMapping *mappingEnd = mapping + size;
+ while (PyDict_Next(kwds, &pos, &key, &value) != 0) {
+ auto it = findMapping(mapping, mappingEnd, key);
+ // Missing key: Create a new dict as error context (see signature/errorhandler.py)
+ if (it == mappingEnd) {
+ errInfo.reset(PyDict_New());
+ PyDict_SetItem(errInfo.object(), key, value);
+ return false;
+ }
+ if (pyArgs[it->index] != nullptr) { // duplicate entry, set string as error context
+ errInfo.reset(key);
+ return false;
+ }
+ pyArgs[it->index] = value;
+ }
+ return true;
+}
+
+bool parseConstructorKeywordArguments(PyObject *kwds,
+ const ArgumentNameIndexMapping *mapping, size_t size,
+ Shiboken::AutoDecRef &errInfo, PyObject **pyArgs)
+{
+ assert(kwds);
+ Shiboken::AutoDecRef result(PyDict_New());
+ PyObject *key{};
+ PyObject *value{};
+ Py_ssize_t pos = 0;
+ const ArgumentNameIndexMapping *mappingEnd = mapping + size;
+ while (PyDict_Next(kwds, &pos, &key, &value) != 0) {
+ auto it = findMapping(mapping, mappingEnd, key);
+ // Ignore missing key, assuming it is a property to be handled later
+ if (it != mappingEnd) {
+ // duplicate entry, set string as error context (see signature/errorhandler.py)
+ if (pyArgs[it->index] != nullptr) {
+ errInfo.reset(key);
+ return false;
+ }
+ pyArgs[it->index] = value;
+ } else {
+ PyDict_SetItem(result.object(), key, value);
+ }
+ }
+ errInfo.reset(result.release());
+ return true;
+}
+
+} // namespace Shiboken
--- /dev/null
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef SBK_BINDINGUTILS
+#define SBK_BINDINGUTILS
+
+#include "sbkpython.h"
+#include "shibokenmacros.h"
+
+namespace Shiboken {
+struct AutoDecRef;
+
+/// Maps a keyword argument by name to its parameter index
+struct ArgumentNameIndexMapping
+{
+ const char *name;
+ int index;
+};
+
+/// Function binding helper: Parse the keyword arguments in dict \a kwds
+/// according to \a mapping (name->index) and store them in array \a pyArgs
+/// under their index. Fails if an entry is missing or duplicate entries
+/// occur.
+LIBSHIBOKEN_API bool
+ parseKeywordArguments(PyObject *kwds,
+ const ArgumentNameIndexMapping *mapping, size_t size,
+ Shiboken::AutoDecRef &errInfo, PyObject **pyArgs);
+
+/// Function binding helper: Parse the keyword arguments of a QObject constructor
+/// in dict \a kwds according to \a mapping (name->index) and store them in array
+/// \a pyArgs under their index. Fails if duplicate entries occur. Unmapped entries
+/// (QObject properties) are stored in a dict in errInfo for further processing.
+LIBSHIBOKEN_API bool
+ parseConstructorKeywordArguments(PyObject *kwds,
+ const ArgumentNameIndexMapping *mapping, size_t size,
+ Shiboken::AutoDecRef &errInfo, PyObject **pyArgs);
+
+} // namespace Shiboken
+
+#endif // SBK_BINDINGUTILS
if (!PyDict_Check(pyIn))
return false;
- PyObject *key;
- PyObject *value;
+ PyObject *key{};
+ PyObject *value{};
Py_ssize_t pos = 0;
while (PyDict_Next(pyIn, &pos, &key, &value)) {
if (!PyObject_TypeCheck(key, keyType))
assert(pyIn);
if (!PyDict_Check(pyIn))
return false;
- PyObject *key;
- PyObject *value;
+ PyObject *key{};
+ PyObject *value{};
Py_ssize_t pos = 0;
while (PyDict_Next(pyIn, &pos, &key, &value)) {
if (keyCheckExact) {
assert(pyIn);
if (!PyDict_Check(pyIn))
return false;
- PyObject *key;
- PyObject *values;
+ PyObject *key{};
+ PyObject *values{};
Py_ssize_t pos = 0;
while (PyDict_Next(pyIn, &pos, &key, &values)) {
if (keyCheckExact) {
m_converter = getConverter(typeName);
if (!m_converter)
return;
- const Py_ssize_t len = strlen(typeName);
+ const auto len = strlen(typeName);
char lastChar = typeName[len -1];
if (lastChar == '&') {
m_type = ReferenceConversion;
explicit SpecificConverter(const char *typeName);
- inline SbkConverter *converter() { return m_converter; }
- inline operator SbkConverter *() const { return m_converter; }
+ SbkConverter *converter() { return m_converter; }
+ operator SbkConverter *() const { return m_converter; }
- inline bool isValid() { return m_type != InvalidConversion; }
- inline operator bool() const { return m_type != InvalidConversion; }
+ bool isValid() { return m_type != InvalidConversion; }
+ operator bool() const { return m_type != InvalidConversion; }
- inline Type conversionType() { return m_type; }
+ Type conversionType() { return m_type; }
PyObject *toPython(const void *cppIn);
void toCpp(PyObject *pyIn, void *cppOut);
#include "sbktypefactory.h"
#include <cstring>
-#include <vector>
-#include <sstream>
using namespace Shiboken;
init_enum();
static PyTypeObject *meta = getPyEnumMeta();
- return Py_TYPE(pyTypeObj) == reinterpret_cast<PyTypeObject *>(meta);
+ return Py_TYPE(pyTypeObj) == meta;
}
PyObject *getEnumItemFromValue(PyTypeObject *enumType, EnumValueType itemValue)
priv->flagsConverter = flagsConverter;
}
-static PyTypeObject *createEnumForPython(PyObject *scopeOrModule,
- const char *fullName,
- PyObject *pyEnumItems)
+static void setModuleAndQualnameOnType(PyObject *type, const char *fullName)
{
const char *colon = strchr(fullName, ':');
assert(colon);
int mlen = qual - mod - 1;
AutoDecRef module(Shiboken::String::fromCString(mod, mlen));
AutoDecRef qualname(Shiboken::String::fromCString(qual));
- const char *dot = strrchr(qual, '.');
- AutoDecRef name(Shiboken::String::fromCString(dot ? dot + 1 : qual));
+
+ PyObject_SetAttr(type, Shiboken::PyMagicName::module(), module);
+ PyObject_SetAttr(type, Shiboken::PyMagicName::qualname(), qualname);
+}
+
+static PyTypeObject *createEnumForPython(PyObject *scopeOrModule,
+ const char *fullName,
+ PyObject *pyEnumItems)
+{
+ const char *dot = strrchr(fullName, '.');
+ AutoDecRef name(Shiboken::String::fromCString(dot ? dot + 1 : fullName));
static PyObject *enumName = String::createStaticString("IntEnum");
if (PyType_Check(scopeOrModule)) {
// For global objects, we have no good solution, yet where to put the int info.
- auto type = reinterpret_cast<PyTypeObject *>(scopeOrModule);
+ auto *type = reinterpret_cast<PyTypeObject *>(scopeOrModule);
auto *sotp = PepType_SOTP(type);
if (!sotp->enumFlagsDict)
initEnumFlagsDict(type);
}
}
- auto *newType = reinterpret_cast<PyTypeObject *>(obNewType);
- PyObject_SetAttr(obNewType, PyMagicName::qualname(), qualname);
- PyObject_SetAttr(obNewType, PyMagicName::module(), module);
+ setModuleAndQualnameOnType(obNewType, fullName);
// See if we should re-introduce shortcuts in the enclosing object.
const bool useGlobalShortcut = (Enum::enumOption & Enum::ENOPT_GLOBAL_SHORTCUT) != 0;
}
}
- return newType;
+ return reinterpret_cast<PyTypeObject *>(obNewType);
}
template <typename IntT>
return createPythonEnumHelper(module, fullName, enumItemStrings, enumValues);
}
+PyTypeObject *createPythonEnum(const char *fullName, PyObject *pyEnumItems,
+ const char *enumTypeName, PyObject *callDict)
+{
+ SBK_UNUSED(getPyEnumMeta());
+ AutoDecRef PyEnumTypeName(Shiboken::String::fromCString(enumTypeName));
+ AutoDecRef PyEnumType(PyObject_GetAttr(PyEnumModule, PyEnumTypeName));
+ if (!PyEnumType) {
+ PyErr_Format(PyExc_RuntimeError, "Failed to get enum type %s", enumTypeName);
+ return nullptr;
+ }
+
+ const char *dot = strrchr(fullName, '.');
+ AutoDecRef name(Shiboken::String::fromCString(dot ? dot + 1 : fullName));
+ AutoDecRef callArgs(Py_BuildValue("(OO)", name.object(), pyEnumItems));
+ auto *newType = PyObject_Call(PyEnumType, callArgs, callDict);
+
+ setModuleAndQualnameOnType(newType, fullName);
+
+ return reinterpret_cast<PyTypeObject *>(newType);
+}
+
} // namespace Shiboken::Enum
return createPythonEnum(obScope, fullName, enumItemStrings, enumValues);
}
+/**
+ * @brief Creates a Python enum type from a set of provided key/values pairs
+ *
+ * @param fullName The full name (including module and package depth) to be used for the newly
+ * created enum type.
+ * @param pyEnumItems The key/value pairs to be used for the enum.
+ * @param enumTypeName The name of the enum type to be used (i.e., "PyIntEnum", "PyFlag", etc)
+ * from Python's enum module.
+ * @param callDict The dictionary to be used for the call, allowing for additional keyword
+ * arguments to be passed, such as "boundary=KEEP".
+ */
+LIBSHIBOKEN_API PyTypeObject *createPythonEnum(const char *fullName,
+ PyObject *pyEnumItems,
+ const char *enumTypeName = "Enum",
+ PyObject *callDict = nullptr);
+
} // namespace Shiboken::Enum
#endif // SKB_PYENUM_H
name);
}
+void setInstantiateNamespace(const char *name)
+{
+ PyErr_Format(PyExc_NotImplementedError, "Namespace '%s' cannot be instantiated.", name);
+}
+
+void setInstantiateNonConstructible(const char *name)
+{
+ PyErr_Format(PyExc_NotImplementedError, "Class '%s' cannot be instantiated.", name);
+}
+
void setInvalidTypeDeletion(const char *name)
{
PyErr_Format(PyExc_TypeError, "'%s' may not be deleted", name);
Py_ssize_t maxValue);
LIBSHIBOKEN_API void setInstantiateAbstractClass(const char *name);
LIBSHIBOKEN_API void setInstantiateAbstractClassDisabledWrapper(const char *name);
+LIBSHIBOKEN_API void setInstantiateNamespace(const char *name);
+LIBSHIBOKEN_API void setInstantiateNonConstructible(const char *name);
LIBSHIBOKEN_API void setInvalidTypeDeletion(const char *name);
LIBSHIBOKEN_API void setOperatorNotImplemented();
LIBSHIBOKEN_API void setPureVirtualMethodError(const char *name);
PyErr_Restore(error_type, error_value, error_traceback);
}
-// Python 3.13
-static int const LOAD_ATTR_313 = 82;
-static int const CALL_313 = 53;
-static int const PUSH_NULL_313 = 34;
-// Python 3.12
-static int const CALL_312 = 171;
-// Python 3.11
+// OpCodes: Adapt for each Python version by checking the defines in the generated header opcode_ids.h
+// egrep '( LOAD_ATTR | CALL | PUSH_NULL )' opcode_ids.h
+// See also test sources/pyside6/tests/pysidetest/enum_test.py
+
+static int constexpr LOAD_ATTR_OpCode(long pyVersion)
+{
+ if (pyVersion >= 0x030E00) // 3.14
+ return 80;
+ if (pyVersion >= 0x030D00) // 3.13
+ return 82;
+ return 106;
+}
+
+static int constexpr CALL_OpCode(long pyVersion)
+{
+ if (pyVersion >= 0x030E00) // 3.14
+ return 52;
+ if (pyVersion >= 0x030D00) // 3.13
+ return 53;
+ return 171;
+}
+
+static int constexpr PUSH_NULL_OpCode(long pyVersion)
+{
+ if (pyVersion >= 0x030E00) // 3.14
+ return 33;
+ return 34;
+}
+
static int const PRECALL = 166;
// we have "big instructions" with gaps after them
static int const LOAD_METHOD_GAP_311 = 10 * 2;
// Python 3.7 - 3.10
static int const LOAD_METHOD = 160;
static int const CALL_METHOD = 161;
-// Python 3.6
-static int const CALL_FUNCTION = 131;
-static int const LOAD_ATTR_312 = 106;
-// NoGil (how long will this exist in this form?)
-static int const LOAD_METHOD_NOGIL = 55;
-static int const CALL_METHOD_NOGIL = 72;
static bool currentOpcode_Is_CallMethNoArgs()
{
- static auto number = _PepRuntimeVersion();
- static int LOAD_ATTR = number < 0x030D00 ? LOAD_ATTR_312 : LOAD_ATTR_313;
- static int CALL = number < 0x030D00 ? CALL_312 : CALL_313;
- // PYSIDE-2221: Special case for the NoGil version:
- // Find out if we have such a version.
- // We could also ask the variable `Py_NOGIL`.
+ static const auto number = _PepRuntimeVersion();
static PyObject *flags = PySys_GetObject("flags");
- static bool isNoGil = PyObject_HasAttrString(flags, "nogil");
// We look into the currently active operation if we are going to call
// a method with zero arguments.
auto *frame = PyEval_GetFrame();
char *co_code{};
PyBytes_AsStringAndSize(dec_co_code, &co_code, &code_len);
uint8_t opcode1 = co_code[f_lasti];
- if (isNoGil) {
- uint8_t opcode2 = co_code[f_lasti + 4];
- uint8_t oparg2 = co_code[f_lasti + 6];
- return opcode1 == LOAD_METHOD_NOGIL && opcode2 == CALL_METHOD_NOGIL && oparg2 == 1;
- }
uint8_t opcode2 = co_code[f_lasti + 2];
uint8_t oparg2 = co_code[f_lasti + 3];
- if (number < 0x030B00)
+ if (number < 0x030B00) // pre 3.11
return opcode1 == LOAD_METHOD && opcode2 == CALL_METHOD && oparg2 == 0;
- if (number < 0x030C00) {
+ if (number < 0x030C00) { // pre 3.12
// With Python 3.11, the opcodes get bigger and change a bit.
// Note: The new adaptive opcodes are elegantly hidden and we
// don't need to take care of them.
if (opcode1 == LOAD_METHOD)
f_lasti += LOAD_METHOD_GAP_311;
- else if (opcode1 == LOAD_ATTR_312)
+ else if (opcode1 == LOAD_ATTR_OpCode(0x030C00)) // 3.12
f_lasti += LOAD_ATTR_GAP_311;
else
return false;
// With Python 3.12, the opcodes get again bigger and change a bit.
// Note: The new adaptive opcodes are elegantly hidden and we
// don't need to take care of them.
- if (opcode1 == LOAD_ATTR)
+ if (opcode1 == LOAD_ATTR_OpCode(number))
f_lasti += LOAD_ATTR_GAP;
else
return false;
- if (number >= 0x030D00) {
+ if (number >= 0x030D00) { // starting with 3.13
int opcode3 = co_code[f_lasti + 2];
- if (opcode3 == PUSH_NULL_313)
+ if (opcode3 == PUSH_NULL_OpCode(number))
f_lasti += 2;
}
opcode2 = co_code[f_lasti + 2];
oparg2 = co_code[f_lasti + 3];
- return opcode2 == CALL && oparg2 == 0;
+ return opcode2 == CALL_OpCode(number) && oparg2 == 0;
}
void initEnumFlagsDict(PyTypeObject *type)
auto initFunc = tcStruct.func;
PyTypeObject *type = initFunc(modOrType);
auto name = names.substr(startPos);
- PyObject_SetAttrString(modOrType, name.data(), reinterpret_cast<PyObject *>(type));
+ AutoDecRef nameP(PyUnicode_FromStringAndSize(name.data(), name.size()));
+ PyObject_SetAttr(modOrType, nameP, reinterpret_cast<PyObject *>(type));
}
static void incarnateSubtypes(PyObject *module,
static int const LOAD_CONST_313 = 83;
static int const IMPORT_NAME_313 = 75;
+// OpCodes: Adapt for each Python version by checking the defines in the generated header opcode_ids.h
+// egrep '( LOAD_CONST | IMPORT_NAME )' opcode_ids.h
+
+static int constexpr LOAD_CONST_OpCode(long pyVersion)
+{
+ if (pyVersion >= 0x030E00) // 3.14
+ return 82;
+ if (pyVersion >= 0x030D00) // 3.13
+ return 83;
+ return 100;
+}
+
+static int constexpr IMPORT_NAME_OpCode(long pyVersion)
+{
+ if (pyVersion >= 0x030E00) // 3.14
+ return 73;
+ if (pyVersion >= 0x030D00) // 3.13
+ return 75;
+ return 108;
+}
+
static bool isImportStar(PyObject *module)
{
// Find out whether we have a star import. This must work even
static PyObject *const _co_consts = Shiboken::String::createStaticString("co_consts");
static PyObject *const _co_names = Shiboken::String::createStaticString("co_names");
- static int LOAD_CONST = _PepRuntimeVersion() < 0x030D00 ? LOAD_CONST_312 : LOAD_CONST_313;
- static int IMPORT_NAME = _PepRuntimeVersion() < 0x030D00 ? IMPORT_NAME_312 : IMPORT_NAME_313;
+
+ static const int LOAD_CONST = LOAD_CONST_OpCode(_PepRuntimeVersion());
+ static const int IMPORT_NAME = IMPORT_NAME_OpCode(_PepRuntimeVersion());
auto *obFrame = reinterpret_cast<PyObject *>(PyEval_GetFrame());
if (obFrame == nullptr)
// Replace the dictionary of a module by a different dict.
// The dict should be filled with the content of the old dict, before.
// Reason: Creating a module dict with __missing__ support.
-typedef struct {
+struct StartOf_PyModuleObject {
PyObject_HEAD
PyObject *md_dict;
-} StartOf_PyModuleObject;
+};
bool replaceModuleDict(PyObject *module, PyObject *modClass, PyObject *dict)
{
static PyObject *initPathLike()
{
PyObject *PathLike{};
- auto osmodule = PyImport_ImportModule("os");
+ auto *osmodule = PyImport_ImportModule("os");
if (osmodule == nullptr
|| (PathLike = PyObject_GetAttrString(osmodule, "PathLike")) == nullptr) {
PyErr_Print();
#include "helper.h"
#include "pyobjectholder.h"
#include "sbkarrayconverter.h"
+#include "sbkbindingutils.h"
#include "sbkconverter.h"
#include "sbkenum.h"
#include "sbkerrors.h"
if (props == nullptr)
Py_RETURN_NONE;
- int flags = PyCFunction_GET_FLAGS(obfunc);
- PyObject *func_kind;
+ int flags = PyCFunction_GetFlags(obfunc);
+ PyObject *func_kind{};
if (PyModule_Check(obtype_mod.object()))
func_kind = PyName::function();
else if (flags & METH_CLASS)
static PyObject *get_signature(PyObject * /* self */, PyObject *args)
{
- PyObject *ob;
+ PyObject *ob{};
PyObject *modifier = nullptr;
if (!PyArg_ParseTuple(args, "O|O", &ob, &modifier))
// feature_import did not handle it, so call the normal import.
Py_DECREF(ret);
static PyObject *builtins = PyEval_GetBuiltins();
- PyObject *import_func = PyDict_GetItemString(builtins, "__orig_import__");
- if (import_func == nullptr) {
+ PyObject *origImportFunc = PyDict_GetItemString(builtins, "__orig_import__");
+ if (origImportFunc == nullptr) {
Py_FatalError("builtins has no \"__orig_import__\" function");
}
- ret = PyObject_Call(import_func, args, kwds);
+ // PYSIDE-3054: Instead of just calling the original import, we temporarily
+ // reset the whole import function to the previous version.
+ // This prevents unforeseen recursions like in settrace.
+ PyObject *featureImportFunc = PyDict_GetItemString(builtins, "__import__");
+ Py_INCREF(origImportFunc);
+ Py_INCREF(featureImportFunc);
+ PyDict_SetItemString(builtins, "__import__", origImportFunc);
+ ret = PyObject_Call(origImportFunc, args, kwds);
if (ret) {
// PYSIDE-2029: Intercept after the import to search for PySide usage.
PyObject *post = PyObject_CallFunctionObjArgs(pyside_globals->feature_imported_func,
Py_XDECREF(post);
if (post == nullptr) {
Py_DECREF(ret);
- return nullptr;
+ ret = nullptr;
}
}
+ PyDict_SetItemString(builtins, "__import__", featureImportFunc);
+ Py_DECREF(origImportFunc);
+ Py_DECREF(featureImportFunc);
return ret;
}
// Finally, generate the correct path expression.
char _buf[250 + 1] = {};
if (prop_name) {
- auto _prop_name = String::toCString(prop_name);
+ const auto *_prop_name = String::toCString(prop_name);
if (is_class_prop)
snprintf(_buf, sizeof(_buf), "%s.__dict__['%s'].fset", _path, _prop_name);
else
snprintf(_buf, sizeof(_buf), "%s.%s.fset", _path, _prop_name);
}
else {
- auto _name = String::toCString(name);
+ const auto *_name = String::toCString(name);
snprintf(_buf, sizeof(_buf), "%s.%s", _path, _name);
}
return String::fromCString(_buf);
PyObject *_get_class_of_cf(PyObject *ob_cf)
{
- PyObject *selftype = PyCFunction_GET_SELF(ob_cf);
+ PyObject *selftype = PyCFunction_GetSelf(ob_cf);
if (selftype == nullptr) {
selftype = PyDict_GetItem(pyside_globals->map_dict, ob_cf);
if (selftype == nullptr) {
if __name__ == "__main__":
- main()
+ main()
-shiboken_library_soversion = str(@shiboken6_library_so_version@)
+shiboken_library_soversion = "@shiboken6_library_so_version@"
version = "@FINAL_PACKAGE_VERSION@"
version_info = (@shiboken_MAJOR_VERSION@, @shiboken_MINOR_VERSION@, @shiboken_MICRO_VERSION@, "@shiboken_PRE_RELEASE_VERSION_TYPE@", "@shiboken_PRE_RELEASE_VERSION@")
try:
source = inspect.getsource(module)
except TypeError:
- # this is a builtin module like sys
+ # This is a builtin module like sys.
return False
except OSError:
- # this is a module withot source file
+ # This is a module without source file.
return False
except SyntaxError:
# PYSIDE-2189: A UnicodeError happens in tokenize.py in find_cookie
# This is undocumented and a Python error, seen in Python 3.10.2 on Windows,
# importing `pythoncom` of the win32 package.
return False
- except Exception:
- # PYSIDE-2393: pytest behaves weird when allowing any other error.
- return False
return "PySide6" in source
It also implements them in this file. The configurations are
used literally as strings like "signature", "existence", etc.
"""
-
+# flake8: noqa E:731
import inspect
+import operator
+import sys
+import types
import typing
+from functools import reduce
from types import SimpleNamespace
from textwrap import dedent
-from shibokensupport.signature.mapping import ellipsis, missing_optional_return
+from shibokensupport.signature.mapping import ellipsis, missing_optional_return, PlaceholderType
from shibokensupport.signature.parser import using_snake_case
from shibokensupport.signature import make_snake_case_name
+from collections.abc import Sequence, Iterable
DEFAULT_PARAM_KIND = inspect.Parameter.POSITIONAL_ONLY
+def formatannotation(annotation, base_module=None):
+ if getattr(annotation, '__module__', None) == 'typing':
+ return repr(annotation).replace('typing.', '')
+ if isinstance(annotation, types.GenericAlias):
+ return str(annotation)
+ if isinstance(annotation, type):
+ if annotation.__module__ in ('builtins', base_module):
+ return annotation.__qualname__
+ return annotation.__module__ + '.' + annotation.__qualname__
+ return repr(annotation)
+
+
+# PYSIDE-3012: Patching Python < 3.9.8 or Python < 3.10.1
+def install_typing_patch():
+ v = sys.version_info[:3]
+ if v[1] == 9 and v[2] < 8 or v[1] == 10 and v[2] < 1:
+ inspect.formatannotation = formatannotation
+
+
+install_typing_patch()
+
+
class SignatureLayout(SimpleNamespace):
"""
Configure a signature.
parameter_names=False)
-def define_nameless_parameter():
- """
- Create Nameless Parameters
-
- A nameless parameter has a reduced string representation.
- This is done by cloning the parameter type and overwriting its
- __str__ method. The inner structure is still a valid parameter.
- """
- def __str__(self):
- # for Python 2, we must change self to be an instance of P
- klass = self.__class__
- self.__class__ = P
- txt = P.__str__(self)
- self.__class__ = klass
- txt = txt[txt.index(":") + 1:].strip() if ":" in txt else txt
- return txt
-
- P = inspect.Parameter
- newname = "NamelessParameter"
- bases = P.__bases__
- body = dict(P.__dict__) # get rid of mappingproxy
- if "__slots__" in body:
- # __slots__ would create duplicates
- for name in body["__slots__"]:
- del body[name]
- body["__str__"] = __str__
- return type(newname, bases, body)
-
-
-NamelessParameter = define_nameless_parameter()
-
-"""
-Note on the "Optional" feature:
-
-When an annotation has a default value that is None, then the
-type has to be wrapped into "typing.Optional".
-
-Note that only the None value creates an Optional expression,
-because the None leaves the domain of the variable.
-Defaults like integer values are ignored: They stay in the domain.
-
-That information would be lost when we use the "..." convention.
-
-Note that the typing module has the remarkable expansion
-
- Optional[T] is Union[T, NoneType]
-
-We want to avoid that when generating the .pyi file.
-This is done by a regex in pyi_generator.py .
-The following would work in Python 3, but this is a version-dependent
-hack that also won't work in Python 2 and would be _very_ complex.
-"""
-# import sys
-# if sys.version_info[0] == 3:
-# class hugo(list):pass
-# typing._normalize_alias["hugo"] = "Optional"
-# Optional = typing._alias(hugo, typing.T, inst=False)
-# else:
-# Optional = typing.Optional
-
-
-def make_signature_nameless(signature):
- """
- Make a Signature Nameless
-
- We use an existing signature and change the type of its parameters.
- The signature looks different, but is totally intact.
- """
- for key in signature.parameters.keys():
- signature.parameters[key].__class__ = NamelessParameter
-
-
_POSITIONAL_ONLY = inspect.Parameter.POSITIONAL_ONLY # noqa E:201
_POSITIONAL_OR_KEYWORD = inspect.Parameter.POSITIONAL_OR_KEYWORD # noqa E:201
_VAR_POSITIONAL = inspect.Parameter.VAR_POSITIONAL # noqa E:201
_VAR_KEYWORD = inspect.Parameter.VAR_KEYWORD # noqa E:201
_empty = inspect.Parameter.empty # noqa E:201
-
+# PYSIDE-3098: Iterable and Sequence can occur together in an overload.
+# This needs sorting in order of generality.
+# This happened in the NumPy support of PySide 6.10. Note that the ordering
+# of methods there is completely different and unrelated to this mypy sorting.
default_weights = {
typing.Any: 1000, # noqa E:241
+ Iterable: 500, # noqa E:241
+ Sequence: 400, # noqa E:241
bool: 101, # noqa E:241
int: 102, # noqa E:241
float: 103, # noqa E:241
}
+_ignore_mro = type, None, typing.Any, typing.TypeVar, typing.Type[PlaceholderType]
+mro_len = lambda ann: len(ann.mro()) if ann not in _ignore_mro and hasattr(ann, "mro") else 0
+
+
def get_ordering_key(anno):
"""
This is the main sorting algorithm for annotations.
A special case are numeric types, which have also an ordering between them.
They can be handled separately, since they are all of the shortest mro.
+
+ PYSIDE-3012: For some reason, we failed to transform `Union[a, b]` directly
+ into `a | b`. Something unknown about comparison must be different.
+ Therefore the transform function was put on top.
+ XXX Get rid of the function and document the problem thoroughly.
"""
typing_type = typing.get_origin(anno)
is_union = typing_type is typing.Union
# Normal: Use the union arg with the shortest mro().
leng = 9999
for ann in typing_args:
- lng = len(ann.mro())
+ lng = mro_len(ann)
if lng < leng:
leng = lng
anno = ann
else:
- leng = len(anno.mro()) if anno not in (type, None, typing.Any) else 0
+ leng = mro_len(anno)
parts = 1
if anno in default_weights:
leng = - default_weights[anno]
return signatures
-def best_to_remove(signatures, idx1, idx2, name=None):
+def best_to_remove(signatures, idx1, idx2, name):
# Both have identical annotation.
sig1 = signatures[idx1]
sig2 = signatures[idx2]
ra1 = sig1.return_annotation
ra2 = sig2.return_annotation
- # Both have equal return annotation.
+ # Both have equal return annotations
if ra1 == ra2:
for p1, p2 in zip(sig1.parameters.values(), sig2.parameters.values()):
- # Use the first with a default.
+ # Keep the first with a default.
if p1.default is not _empty or p2.default is not _empty:
- return idx1 if p1.default is not _empty else idx2
- # Use the one with a return annotation.
- return idx1 if ra1 is not None else idx2
-
-
-def _remove_ambiguous_signatures_body(signatures, name=None):
+ # Note: We return what to remove!
+ return idx2 if p1.default is not _empty else idx1
+ if ra1 and ra2:
+ # Both have a return annotation.
+ # Remove the probably uglier of the two. This is likely to be the
+ # first one without effort because i.E. arg1 comes early in sorting.
+ return idx1
+ # Remove the one without a return annotation.
+ return idx1 if ra2 is not None else idx2
+
+
+def _remove_ambiguous_signatures_body(signatures, name):
# By the sorting of signatures, duplicates will always be adjacent.
last_ann = None
last_idx = -1
return True, new_sigs
-def is_inconsistent_overload(signatures):
- count = 0
- for sig in signatures:
- count += 1 if "self" in sig.parameters else 0
- return count != 0 and count != len(signatures)
-
-
-def remove_ambiguous_signatures(signatures, name=None):
+def remove_ambiguous_signatures(signatures, name):
# This may run more than once because of indexing.
- found, new_sigs = _remove_ambiguous_signatures_body(signatures)
+ found, new_sigs = _remove_ambiguous_signatures_body(signatures, name)
while found:
- found, new_sigs = _remove_ambiguous_signatures_body(new_sigs)
- # Python cannot handle mixed methods and classmethods. Remove the latter.
- if is_inconsistent_overload(signatures):
- new_sigs = list(sig for sig in new_sigs if "self" in sig.parameters)
+ found, new_sigs = _remove_ambiguous_signatures_body(new_sigs, name)
return new_sigs
-def create_signature(props, key):
+def create_signature_union(props, key):
if not props:
# empty signatures string
return
if isinstance(props["multi"], list):
# multi sig: call recursively.
# For debugging: Print the name!
- # name = props["multi"][0]["fullname"]
- res = list(create_signature(elem, key) for elem in props["multi"])
+ name = props["multi"][0]["fullname"]
+ res = list(create_signature_union(elem, key) for elem in props["multi"])
# PYSIDE-2846: Sort multi-signatures by inheritance in order to avoid shadowing.
res = sort_by_inheritance(res)
- res = remove_ambiguous_signatures(res, name=None)
+ res = remove_ambiguous_signatures(res, name)
return res if len(res) > 1 else res[0]
if type(key) is tuple:
del annotations["return"]
# Build a signature.
- kind = DEFAULT_PARAM_KIND
+ kind = last = DEFAULT_PARAM_KIND
params = []
snake_flag = using_snake_case()
if default is not _empty:
if kind != _KEYWORD_ONLY:
kind = _POSITIONAL_OR_KEYWORD
+ if last == _VAR_POSITIONAL:
+ kind = _KEYWORD_ONLY
if default is None:
+ ann = typing.Union[ann]
ann = typing.Optional[ann]
if default is not _empty and layout.ellipsis:
default = ellipsis
continue
if snake_flag:
name = make_snake_case_name(name)
+ last = kind
param = inspect.Parameter(name, kind, annotation=ann, default=default)
params.append(param)
ret_anno = annotations.get('return', _empty)
if ret_anno is not _empty and props["fullname"] in missing_optional_return:
+ ret_anno = typing.Union[ret_anno]
ret_anno = typing.Optional[ret_anno]
- sig = inspect.Signature(params,
- return_annotation=ret_anno,
- __validate_parameters__=False)
-
- # the special case of nameless parameters
- if not layout.parameter_names:
- make_signature_nameless(sig)
- return sig
+ return inspect.Signature(params, return_annotation=ret_anno,
+ __validate_parameters__=False)
+
+
+def transform(signature):
+ # Change the annotations of the parameters to use "|" syntax.
+ params = []
+ changed = False
+ for idx, param in enumerate(signature.parameters.values()):
+ ann = param.annotation
+ if typing.get_origin(ann) is typing.Union:
+ args = typing.get_args(ann)
+ ann = reduce(operator.or_, args)
+ param = param.replace(annotation=ann)
+ changed = True
+ params.append(param)
+ ann = signature.return_annotation
+ if typing.get_origin(ann) is typing.Union:
+ args = typing.get_args(ann)
+ ann = reduce(operator.or_, args)
+ changed = True
+ return signature.replace(parameters=params, return_annotation=ann) if changed else signature
+
+
+def create_signature(props, key):
+ res = create_signature_union(props, key)
+ if type(res) is list:
+ for idx, sig in enumerate(res):
+ res[idx] = transform(sig)
+ else:
+ res = transform(res)
+ return res
+
+
+if sys.version_info[:2] < (3, 10):
+ create_signature = create_signature_union # noqa F:811
# end of file
import inspect
import sys
import types
-import typing
+import collections
from shibokensupport.signature import get_signature as get_sig
from shibokensupport.signature.layout import DEFAULT_PARAM_KIND
from enum import Enum
return thing and type(thing) in (Signal, SignalInstance)
+def is_inconsistent_overload(signatures):
+ if not isinstance(signatures, list):
+ return False
+ count = 0
+ for sig in signatures:
+ count += 1 if "self" in sig.parameters else 0
+ return count != 0 and count != len(signatures)
+
+
class ExactEnumerator:
"""
ExactEnumerator enumerates all signatures in a module as they are.
name = base.__module__ + "." + name
bases_list.append(name)
bases_str = ', '.join(bases_list)
- class_str = f"{class_name}({bases_str})"
+ class_str = f"{class_name}" if bases_str == "object" else f"{class_name}({bases_str})"
# class_members = inspect.getmembers(klass)
# gives us also the inherited things.
class_members = sorted(list(klass.__dict__.items()))
if decorator in self.collision_track:
decorator = f"builtins.{decorator}"
signature = self.get_signature(func, decorator)
+ incon_err = False
+ if is_inconsistent_overload(signature):
+ incon_err = True
# PYSIDE-2846: Special cases of signatures which inherit from object.
_self = inspect.Parameter("self", DEFAULT_PARAM_KIND)
if func_name == "__dir__":
- signature = inspect.Signature([_self], return_annotation=typing.Iterable[str])
+ signature = inspect.Signature([_self], return_annotation=collections.abc.Iterable[str])
elif func_name == "__repr__":
signature = inspect.Signature([_self], return_annotation=str)
if signature is not None:
aug_ass = func in self.mypy_aug_ass_errors
- with self.fmt.function(func_name, signature, decorator, aug_ass) as key:
+ with self.fmt.function(func_name, signature, decorator, aug_ass, incon_err) as key:
ret[key] = signature
del self.func
return ret
import inspect
import io
import logging
+import os
import re
import sys
import typing
brace_searcher = re.compile(brace_pat, flags=re.VERBOSE)
split = brace_searcher.split
+ @classmethod
+ def last_fixups(cls, source):
+ # PYSIDE-2517: findChild/findChildren type hints:
+ # PlaceholderType fix to avoid the '~' from TypeVar.__repr__
+ if "~PlaceholderType" in source:
+ source = source.replace("~PlaceholderType", "PlaceholderType")
+ # Replace all "NoneType" strings by "None" which is a typing convention.
+ return source.replace("NoneType", "None")
+
+ # To be removed when minimum version is 3.10:
@classmethod
def optional_replacer(cls, source):
# PYSIDE-2517: findChild/findChildren type hints:
- # PlaceHolderType fix to avoid the '~' from TypeVar.__repr__
- if "~PlaceHolderType" in source:
- source = source.replace("~PlaceHolderType", "PlaceHolderType")
+ # PlaceholderType fix to avoid the '~' from TypeVar.__repr__
+ if "~PlaceholderType" in source:
+ source = source.replace("~PlaceholderType", "PlaceholderType")
while match := cls.opt_uni_searcher.search(source):
start = match.start()
# Note: this list is interspersed with "," and surrounded by "", see parser.py
parts = [x.strip() for x in cls.split(body) if x.strip() not in ("", ",")]
if name == "typing.Optional":
- parts.append("None")
+ parts.append("None ")
res = " | ".join(parts)
source = source[: start] + res + source[end :]
# Replace all "NoneType" strings by "None" which is a typing convention.
return source.replace("NoneType", "None")
+ if sys.version_info[:2] < (3, 10):
+ last_fixups = optional_replacer
+
# self.level is maintained by enum_sig.py
# self.is_method() is true for non-plain functions.
yield
@contextmanager
- def function(self, func_name, signature, decorator=None, aug_ass=None):
+ def function(self, func_name, signature, decorator=None, aug_ass=None, incon_err=None):
if func_name == "__init__":
self.print()
key = func_name
spaces = indent * self.level
err_ignore = " # type: ignore[misc]"
+ if incon_err:
+ err_ignore = " # type: ignore[misc, overload-cannot-match]"
if isinstance(signature, list):
# PYSIDE-2846: Disable errors in augmented assignments.
- opt_comment = (err_ignore if aug_ass else "")
+ opt_comment = (err_ignore if aug_ass or incon_err else "")
for sig in signature:
self.print(f'{spaces}@typing.overload{opt_comment}')
- opt_comment = ""
- self._function(func_name, sig, spaces)
+ if incon_err:
+ # need to mark all overloads
+ pass
+ else:
+ opt_comment = ""
+ self._function(func_name, sig, spaces, None, opt_comment)
else:
opt_comment = err_ignore if aug_ass else ""
self._function(func_name, signature, spaces, decorator, opt_comment)
# the formatting with the inspect module explicitly removes the `typing` prefix.
signature = self.fix_typing_prefix(signature)
# from now on, the signature will be stringized.
- signature = self.optional_replacer(signature)
+ signature = self.last_fixups(signature)
self.print(f'{spaces}def {func_name}{signature}: ...{opt_comment}')
@contextmanager
(None, ["os"]),
(None, ["enum"]),
(None, ["typing"]),
- ("collections.abc", ["Iterable"]),
+ (None, ["collections.abc"]),
("PySide6.QtCore", ["PyClassProperty", "Signal", "SignalInstance"]),
("shiboken6", ["Shiboken"]),
]
for each in imports:
# PYSIDE-1603: We search text that is a usage of the class `each`,
# but only if the class is not also defined here.
- if (f"class {each}(") not in text:
+ if f"class {each}(" not in text and f"class {each}:" not in text:
if re.search(rf"(\b|@){each}\b([^\s\(:]|\n)", text):
lis.append(each)
# Search if a type is present in the return statement
# We use it only in QtCore at the moment, but this
# could be extended to other modules. (must import QObject then)
if import_name == "PySide6.QtCore":
- wr.print("PlaceHolderType = typing.TypeVar(\"PlaceHolderType\", "
+ wr.print("PlaceholderType = typing.TypeVar(\"PlaceholderType\", "
"bound=PySide6.QtCore.QObject)")
wr.print()
else:
import struct
import sys
import typing
+import collections
from pathlib import Path
from typing import TypeVar, Generic
return "..."
+# PYSIDE-3135
+# Using _Self to avoid adding the parameter to typing in versions <3.11
+# Drop this when Python 3.11 is the minimum version
if not hasattr(typing, "Self"):
@typing._SpecialForm
def Self(self, parameters):
raise TypeError(f"{self} is not subscriptable")
- typing.Self = Self
+ typing._Self = Self
+else:
+ typing._Self = typing.Self
ellipsis = ellipsis()
Point = typing.Tuple[int, int]
Variant = typing.Any
-QImageCleanupFunction = typing.Callable[..., typing.Any]
+QImageCleanupFunction = collections.abc.Callable[..., typing.Any]
# unfortunately, typing.Optional[t] expands to typing.Union[t, NoneType]
# Until we can force it to create Optional[t] again, we use this.
# PYSIDE-2517: findChild/findChildren type hints:
# Placeholder so it does not trigger an UNDEFINED error while building.
# Later it will be bound to a QObject, within the QtCore types extensions
-PlaceHolderType = TypeVar("PlaceHolderType")
+PlaceholderType = TypeVar("PlaceholderType")
_S = TypeVar("_S")
"int": int,
"List": ArrayLikeVariable,
"Optional": typing.Optional,
- "Iterable": typing.Iterable,
+ "Iterable": collections.abc.Iterable,
"long": int,
"long long": int,
"nullptr": None,
- "PyCallable": typing.Callable[..., typing.Any],
+ "PyCallable": collections.abc.Callable[..., typing.Any],
"PyObject": object,
"PyObject*": object,
"PyArrayObject": ArrayLikeVariable(typing.Any), # numpy
"PyPathLike": typing.Union[str, bytes, os.PathLike[str]],
- "PySequence": typing.Iterable, # important for numpy
+ "PySequence": collections.abc.Iterable, # important for numpy
"PyTypeObject": type,
"QChar": str,
"QHash": typing.Dict,
"short": int,
"signed char": int,
"signed long": int,
+ "std.chrono.milliseconds": int,
+ "std.chrono.seconds": int,
"std.list": typing.List,
"std.map": typing.Dict,
"std.nullptr_t": NoneType,
"NULL": None, # 5.6, MSVC
"nullptr": None, # 5.9
# PYSIDE-2517: findChild/findChildren type hints:
- "PlaceHolderType": typing.TypeVar("PlaceHolderType", bound=PySide6.QtCore.QObject),
+ "PlaceholderType": typing.TypeVar("PlaceholderType", bound=PySide6.QtCore.QObject),
"PyBuffer": typing.Union[bytes, bytearray, memoryview],
"PyByteArray": bytearray,
"PyBytes": typing.Union[bytes, bytearray, memoryview],
# Here we manually set it to map to 'str'.
type_map_tuple.update({("PySide6.QtCore.QObject.setProperty", "char*"): str})
type_map_tuple.update({("PySide6.QtCore.QObject.property", "char*"): str})
+ type_map_tuple.update({("PySide6.QtCore.QObject.inherits", "char*"): str})
+ type_map_tuple.update({("PySide6.QtCore.QObject.connect", "char*"): str})
+ type_map_tuple.update({("PySide6.QtCore.QObject.disconnect", "char*"): str})
+ type_map_tuple.update({("PySide6.QtCore.QObject.receivers", "char*"): str})
+ type_map_tuple.update({("PySide6.QtCore.qtTrId", "char*"): str})
return locals()
"PySide6.QtWidgets.QTableWidget.item",
"PySide6.QtWidgets.QTableWidget.itemAt",
"PySide6.QtWidgets.QTableWidget.mimeData",
+ "PySide6.QtWidgets.QTreeWidget.takeTopLevelItem",
+ "PySide6.QtWidgets.QTreeWidget.topLevelItem",
"PySide6.QtWidgets.QWidget.childAt",
"PySide6.QtWidgets.QWidget.find",
"PySide6.QtWidgets.QWidget.focusProxy",
import sys
import typing
import warnings
+import collections.abc
+import abc
from types import SimpleNamespace
from shibokensupport.signature.mapping import (type_map, type_map_tuple, update_mapping,
# _PepUnicode_AsString: Fix a broken promise
if pyminver and pyminver >= (3, 10):
warnings.warn(f"{p} _PepUnicode_AsString can now be replaced by PyUnicode_AsUTF8! ***")
+ # PYSIDE-3012: Emit a warning when we may simplify layout.py and pyi_generator.py
+ if pyminver and pyminver >= (3, 10):
+ warnings.warn(f"{p} layout.py and pyi_generator.py can now remove old code! ***")
# PYSIDE-1960: Emit a warning when we may remove bufferprocs_py37.(cpp|h)
if pyminver and pyminver >= (3, 11):
warnings.warn(f"{p} The files bufferprocs_py37.(cpp|h) should be removed ASAP! ***")
sys.stdout.flush()
-_cache = {}
+class ArglistParser:
+ def __init__(self):
+ regex = build_brace_pattern(level=3, separators=",")
+ rec = re.compile(regex, flags=re.VERBOSE)
+ self._finditer = rec.finditer
+
+ def parse(self, argstr):
+ return list(x.group(1).strip() for x in self._finditer(argstr))
+
+
+arglistParser = ArglistParser()
def _parse_arglist(argstr):
- # The following is a split re. The string is broken into pieces which are
- # between the recognized strings. Because the re has groups, both the
- # strings and the separators are returned, where the strings are not
- # interesting at all: They are just the commata.
- key = "_parse_arglist"
- if key not in _cache:
- regex = build_brace_pattern(level=3, separators=",")
- _cache[key] = re.compile(regex, flags=re.VERBOSE)
- split = _cache[key].split
- # Note: this list is interspersed with "," and surrounded by ""
- return [x.strip() for x in split(argstr) if x.strip() not in ("", ",")]
+ return arglistParser.parse(argstr)
def _parse_line(line):
def _resolve_value(thing, valtype, line):
if thing in ("0", "None") and valtype:
- if valtype.startswith("PySide6.") or valtype.startswith("typing."):
+ if valtype.startswith(("PySide6.", "typing.", "collections.abc.")):
return None
mapped = type_map.get(valtype)
# typing.Any: '_SpecialForm' object has no attribute '__name__'
# so we fall back to use __name__ before the next condition.
if isinstance(thing, typing.TypeVar):
return get_name(thing)
- if hasattr(thing, "__name__") and thing.__module__ != "typing":
+ if hasattr(thing, "__name__") and thing.__module__ not in ("typing", "collections.abc"):
m = thing.__module__
dot = "." in str(thing) or m not in (thing.__qualname__, "builtins")
name = get_name(thing)
ret = m + "." + name if dot else name
assert (eval(ret, globals(), namespace))
return ret
+ elif type(thing) in (abc.ABCMeta, type):
+ # collections.abc.Sequence without argument is very different from typing.
+ # PYSIDE-3147 - type(typing.Union) is a type, not typing._SpecialForm.
+ return f"{thing.__module__}.{thing.__name__}"
# Note: This captures things from the typing module:
return str(thing)
type_map[thing] = res
return res
except AttributeError:
- # Maybe it was anothr module...
+ # Maybe it was another module...
pass
return None
Currently, the best approximation is types.Sequence.
We want to change that to types.Iterable in the near future.
"""
- return _handle_generic(obj, typing.Sequence)
+ return _handle_generic(obj, collections.abc.Sequence)
def handle_retvar(obj):
ann = 'nullptr' # maps to None
tup = name, ann
arglist[idx] = tup
- annotations[name] = _resolve_type(ann, line, 0, handle_argvar, parsed.funcname)
+ # When the variable name from constructor and the property is the same,
+ # one overwrites the other. This is fixed here and the latter is omitted.
+ # The property variables added in layout.py
+ if name not in annotations:
+ annotations[name] = _resolve_type(ann, line, 0, handle_argvar, parsed.funcname)
if len(tup) == 3:
default = _resolve_value(tup[2], ann, line)
# PYSIDE-2846: When creating signatures, the defaults should be hashable.
for idx, name in enumerate(varnames):
ann = safe_annos[name]
if isinstance(ann, ArrayLikeVariable):
- ann = typing.Sequence[ann.type]
+ ann = collections.abc.Sequence[ann.type]
annos[name] = ann
if not isinstance(ann, ResultVariable):
continue
#include <parser/codemodel.h>
#include <clangparser/compilersupport.h>
-#include <QtCore/QCoreApplication>
-#include <QtCore/QCommandLineOption>
-#include <QtCore/QCommandLineParser>
-#include <QtCore/QDateTime>
-#include <QtCore/QDebug>
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QLibraryInfo>
-#include <QtCore/QVersionNumber>
-#include <QtCore/QXmlStreamWriter>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qcommandlineoption.h>
+#include <QtCore/qcommandlineparser.h>
+#include <QtCore/qdatetime.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qlibraryinfo.h>
+#include <QtCore/qversionnumber.h>
+#include <QtCore/qxmlstream.h>
#include <iostream>
#include <algorithm>
modelindex.h
modifications.cpp modifications.h
modified_constructor.cpp modified_constructor.h
+moveonly.h
multiple_derived.cpp multiple_derived.h
noimplicitconversion.h
nondefaultctor.h
{
delete new DerivedUsingCt(42);
}
+
+int DerivedUsingCt::derivedValue() const
+{
+ return m_derivedValue;
+}
using CtParam::CtParam;
void foo();
+ int derivedValue() const;
+
+private:
+ int m_derivedValue = 37;
};
#endif // DERIVEDUSINGCT_H
--- /dev/null
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef MOVEONLY_H
+#define MOVEONLY_H
+
+#include "libsamplemacros.h"
+
+class MoveOnly
+{
+public:
+ LIBMINIMAL_DISABLE_COPY(MoveOnly)
+ LIBMINIMAL_DEFAULT_MOVE(MoveOnly)
+
+ explicit MoveOnly(int v = 0) noexcept : m_value(v) {}
+ ~MoveOnly() = default;
+
+ int value() const { return m_value; }
+
+private:
+ int m_value;
+};
+
+class MoveOnlyHandler
+{
+public:
+ LIBMINIMAL_DISABLE_COPY(MoveOnlyHandler)
+ LIBMINIMAL_DISABLE_MOVE(MoveOnlyHandler)
+
+ MoveOnlyHandler() noexcept = default;
+ virtual ~MoveOnlyHandler() = default;
+
+ static MoveOnly passMoveOnly(MoveOnly m) { return m; }
+
+ // Test whether compilation succeeds
+ virtual MoveOnly passMoveOnlyVirtually(MoveOnly m) { return m; }
+};
+
+#endif // MOVEONLY_H
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "samplenamespace.h"
+#include "moveonly.h"
#include <iostream>
#include <cstdlib>
<value-type name="Number" />
<smart-pointer-type name="SharedPtr" type="shared" getter="data" ref-count-method="useCount"
- instantiations="Str"/>
+ excluded-instantiations="Integer,int"/>
<value-type name="SmartPtrTester"/>
<typedef-type name="ValueWithUnitIntInch" source="ValueWithUnit<int,LengthUnit::Inch>"/>
${generator_src_dir}/qtdoc/qtxmltosphinx.cpp
${api_extractor_src_dir}/codesniphelpers.cpp
${api_extractor_src_dir}/textstream.cpp
+ ${api_extractor_src_dir}/filecache.cpp
main.cpp)
add_executable(qtxmltosphinx ${qtxmltosphinx_SRC})
#include "qtxmltosphinxinterface.h"
#include "qtxmltosphinx.h"
-#include <QtCore/QCommandLineParser>
-#include <QtCore/QCoreApplication>
-#include <QtCore/QDebug>
-#include <QtCore/QFile>
-#include <QtCore/QLoggingCategory>
+#include <QtCore/qcommandlineparser.h>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qloggingcategory.h>
#include <exception>
#include <iostream>
${generator_src_dir}/qtdoc/qtxmltosphinx.cpp
${api_extractor_src_dir}/codesniphelpers.cpp
${api_extractor_src_dir}/textstream.cpp
+ ${api_extractor_src_dir}/filecache.cpp
qtxmltosphinxtest.cpp
qtxmltosphinxtest.h)
#include "qtxmltosphinxtest.h"
#include "qtxmltosphinx.h"
-#include <QtTest/QTest>
-#include <QtCore/QBuffer>
-#include <QtCore/QDebug>
-#include <QtCore/QLoggingCategory>
+#include <QtTest/qtest.h>
+
+#include <QtCore/qbuffer.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qtemporaryfile.h>
using namespace Qt::StringLiterals;
QBuffer buffer(&file);
QVERIFY(buffer.open(QIODevice::ReadOnly));
QString errorMessage;
- QString actual = QtXmlToSphinx::readSnippet(buffer, id, &errorMessage);
+
+ QTemporaryFile snippetFile(QDir::tempPath() + "/XXXXXX_snippet.txt"_L1);
+ QVERIFY(snippetFile.open());
+ snippetFile.write(file);
+ const QString fileName = snippetFile.fileName();
+ snippetFile.close();
+ QString actual = QtXmlToSphinx::readSnippet(fileName, id, &errorMessage);
QVERIFY2(errorMessage.isEmpty(), qPrintable(errorMessage));
QCOMPARE(actual, expected);
}
#include "qtxmltosphinxinterface.h"
-#include <QtCore/QObject>
+#include <QtCore/qobject.h>
class QtXmlToSphinxTest : public QObject, public QtXmlToSphinxDocGeneratorInterface
{
${CMAKE_CURRENT_BINARY_DIR}/sample/modelindex_wrapper.cpp
${CMAKE_CURRENT_BINARY_DIR}/sample/modifications_wrapper.cpp
${CMAKE_CURRENT_BINARY_DIR}/sample/modifiedconstructor_wrapper.cpp
+${CMAKE_CURRENT_BINARY_DIR}/sample/moveonly_wrapper.cpp
+${CMAKE_CURRENT_BINARY_DIR}/sample/moveonlyhandler_wrapper.cpp
${CMAKE_CURRENT_BINARY_DIR}/sample/noimplicitconversion_wrapper.cpp
${CMAKE_CURRENT_BINARY_DIR}/sample/nondefaultctor_wrapper.cpp
${CMAKE_CURRENT_BINARY_DIR}/sample/objectmodel_wrapper.cpp
'''Test whether a constructor of the base class declared by using works'''
obj = DerivedUsingCt(42)
self.assertEqual(obj.value(), 42)
+ self.assertEqual(obj.derivedValue(), 37)
def testVirtualWithOutParameter(self):
d = Derived()
#include "modelindex.h"
#include "modifications.h"
#include "modified_constructor.h"
+#include "moveonly.h"
#include "multiple_derived.h"
#include "noimplicitconversion.h"
#include "nondefaultctor.h"
@unittest.skipUnless(hasattr(sys, "getrefcount"), f"{sys.implementation.name} has no refcount")
def testParentFromCpp(self):
o = ObjectType()
- self.assertEqual(sys.getrefcount(o), 2)
+ base_count = sys.getrefcount(o) # 1 from 3.14 onwards, previously 2
o.getCppParent().setObjectName('parent')
- self.assertEqual(sys.getrefcount(o), 3)
+ self.assertEqual(sys.getrefcount(o), base_count + 1)
o.getCppParent().setObjectName('parent')
- self.assertEqual(sys.getrefcount(o), 3)
+ self.assertEqual(sys.getrefcount(o), base_count + 1)
o.getCppParent().setObjectName('parent')
- self.assertEqual(sys.getrefcount(o), 3)
+ self.assertEqual(sys.getrefcount(o), base_count + 1)
o.getCppParent().setObjectName('parent')
- self.assertEqual(sys.getrefcount(o), 3)
+ self.assertEqual(sys.getrefcount(o), base_count + 1)
o.getCppParent().setObjectName('parent')
- self.assertEqual(sys.getrefcount(o), 3)
+ self.assertEqual(sys.getrefcount(o), base_count + 1)
o.destroyCppParent()
- self.assertEqual(sys.getrefcount(o), 2)
+ self.assertEqual(sys.getrefcount(o), base_count)
def testNextInFocusChainCycle(self):
parent = ObjectType()
o1 = ObjectType(lt)
o1.setObjectName('o1')
- self.assertEqual(sys.getrefcount(o1), 3)
+ base_ref_count = sys.getrefcount(o1)
lt.takeChild('o1')
- self.assertEqual(sys.getrefcount(o1), 2)
+ self.assertEqual(sys.getrefcount(o1), base_ref_count - 1)
def testSetNullLayout(self):
'''ObjectType.setLayout(0).'''
'''Set the same parent twice to check if the ref continue the same'''
obj = ObjectType()
parent = ObjectType()
- self.assertEqual(sys.getrefcount(obj), 2)
+ base_ref_count = sys.getrefcount(obj)
obj.setParent(parent)
- self.assertEqual(sys.getrefcount(obj), 3)
+ self.assertEqual(sys.getrefcount(obj), base_ref_count + 1)
obj.setParent(parent)
- self.assertEqual(sys.getrefcount(obj), 3)
+ self.assertEqual(sys.getrefcount(obj), base_ref_count + 1)
def testReparentedExtObjectType(self):
'''Reparent children from one extended parent to another.'''
from shibokensupport.signature import get_signature
import typing
+import collections
class PointerPrimitiveTypeTest(unittest.TestCase):
self.assertTrue(found)
ann = sig.parameters["data"].annotation
self.assertEqual(ann.__args__, (int,))
- self.assertTrue(issubclass(ann.__origin__, typing.Iterable))
+ self.assertTrue(issubclass(ann.__origin__, collections.abc.Iterable))
def testReturnVarSignature(self):
# signature="getMargins(int*,int*,int*,int*)const">
from shiboken_paths import init_paths
init_paths()
-from sample import cacheSize
+from sample import wrapperCount
from sample import ProtectedNonPolymorphic, ProtectedVirtualDestructor
from sample import (ProtectedPolymorphic, ProtectedPolymorphicDaughter,
ProtectedPolymorphicGrandDaughter)
def tearDown(self):
# PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
gc.collect()
- self.assertEqual(cacheSize(), 0)
+ self.assertEqual(wrapperCount(), 0)
def testProtectedCall(self):
'''Calls a non-virtual protected method.'''
def tearDown(self):
# PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
gc.collect()
- self.assertEqual(cacheSize(), 0)
+ self.assertEqual(wrapperCount(), 0)
def testProtectedCall(self):
'''Calls a virtual protected method.'''
def tearDown(self):
# PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
gc.collect()
- self.assertEqual(cacheSize(), 0)
+ self.assertEqual(wrapperCount(), 0)
def testProtectedCallWithInstanceCreatedOnCpp(self):
'''Calls a virtual protected method from parent class on an instance created in C++.'''
def tearDown(self):
# PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
gc.collect()
- self.assertEqual(cacheSize(), 0)
+ self.assertEqual(wrapperCount(), 0)
def testVirtualProtectedDtor(self):
'''Original protected virtual destructor is being called.'''
def tearDown(self):
# PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
gc.collect()
- self.assertEqual(cacheSize(), 0)
+ self.assertEqual(wrapperCount(), 0)
def testProtectedMethodWithProtectedEnumArgument(self):
'''Calls protected method with protected enum argument.'''
del self.obj
# PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
gc.collect()
- self.assertEqual(cacheSize(), 0)
+ self.assertEqual(wrapperCount(), 0)
def testProtectedProperty(self):
'''Writes and reads a protected integer property.'''
def testProtectedValueTypePropertyWrapperRegistration(self):
'''Access colocated protected value type property.'''
- cache_size = cacheSize()
+ wrapper_count = wrapperCount()
point = Point(12, 34)
obj = createProtectedProperty()
obj.protectedValueTypeProperty
del obj, point, pointProperty
# PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
gc.collect()
- self.assertEqual(cacheSize(), cache_size)
+ self.assertEqual(wrapperCount(), wrapper_count)
def testProtectedValueTypePointerProperty(self):
'''Writes and reads a protected value type pointer property.'''
def tearDown(self):
# PYSIDE-535: Need to collect garbage in PyPy to trigger deletion
gc.collect()
- self.assertEqual(cacheSize(), 0)
+ self.assertEqual(wrapperCount(), 0)
def testProtectedMethod(self):
'''Calls protected method of a class with a private destructor.'''
# o should be moved from, name is now empty
self.assertEqual(len(o.objectName()), 0)
+ def testMoveOnlyTypes(self):
+ """Pass a move only type (convert using std::move())."""
+ v = 42
+ mo = sample.MoveOnly(v)
+ mo2 = sample.MoveOnlyHandler.passMoveOnly(mo)
+ self.assertEqual(mo2.value(), v)
+
if __name__ == '__main__':
unittest.main()
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+// @snippet module-helpers
+static bool Check2TupleOfNumbers(PyObject *pyIn)
+{
+ if (PySequence_Check(pyIn) == 0 || PySequence_Size(pyIn) != 2)
+ return false;
+ Shiboken::AutoDecRef pyReal(PySequence_GetItem(pyIn, 0));
+ Shiboken::AutoDecRef pyImag(PySequence_GetItem(pyIn, 1));
+ return PyNumber_Check(pyReal) != 0 && PyNumber_Check(pyImag) != 0;
+}
+
+static bool checkPyCapsuleOrPyCObject(PyObject *pyObj)
+{
+ return PyCapsule_CheckExact(pyObj) != 0;
+}
+
+static PyObject* __convertCppValuesToPython(const char **typeNames, void **values, Py_ssize_t size)
+{
+ PyObject* result = PyTuple_New(size);
+ for (Py_ssize_t i = 0; i < size; ++i) {
+ Shiboken::Conversions::SpecificConverter converter(typeNames[i]);
+ PyTuple_SetItem(result, i, converter.toPython(values[i]));
+ }
+ return result;
+}
+// @snippet module-helpers
+
+// @snippet getConversionTypeString
+Shiboken::Conversions::SpecificConverter converter(%1);
+const char *%0 = nullptr;
+switch (converter.conversionType()) {
+case Shiboken::Conversions::SpecificConverter::CopyConversion:
+ %0 = "Copy conversion";
+ break;
+case Shiboken::Conversions::SpecificConverter::PointerConversion:
+ %0 = "Pointer conversion";
+ break;
+case Shiboken::Conversions::SpecificConverter::ReferenceConversion:
+ %0 = "Reference conversion";
+ break;
+default:
+ %0 = "Invalid conversion";
+ break;
+}
+%PYARG_0 = %CONVERTTOPYTHON[const char*](%0);
+// @snippet getConversionTypeString
+
+// @snippet convertValueTypeToCppAndThenToPython
+const char *typeNames[] = { "Point", "Point*", "Point&" };
+void *values[] = { &%1, &%2, &(%3) };
+%PYARG_0 = __convertCppValuesToPython(typeNames, values, 3);
+// @snippet convertValueTypeToCppAndThenToPython
+
+// @snippet convertValueTypeToCppAndThenToPython
+const char *typeNames[] = { "Point", "Point*", "Point&" };
+void *values[] = { &%1, &%2, &%3 };
+%PYARG_0 = __convertCppValuesToPython(typeNames, values, 3);
+// @snippet convertValueTypeToCppAndThenToPython
+
+// @snippet convertObjectTypeToCppAndThenToPython
+const char *typeNames[] = { "ObjectType*", "ObjectType&" };
+void *values[] = { &%1, &%2 };
+%PYARG_0 = __convertCppValuesToPython(typeNames, values, 2);
+// @snippet convertObjectTypeToCppAndThenToPython
+
+// @snippet convertListOfIntegersToCppAndThenToPython
+const char *typeNames[] = { "std::list<int>" };
+void *values[] = { &%1 };
+%PYARG_0 = __convertCppValuesToPython(typeNames, values, 1);
+// @snippet convertListOfIntegersToCppAndThenToPython
+
+// @snippet convertIntegersToCppAndThenToPython
+const char *typeNames[] = { "int", "int" };
+void *values[] = { &%1, &%2 };
+%PYARG_0 = __convertCppValuesToPython(typeNames, values, 2);
+// @snippet convertIntegersToCppAndThenToPython
+
// @snippet intwrapper_add_ints
extern "C" {
static PyObject *Sbk_IntWrapper_add_ints(PyObject * /* self */, PyObject *args)
%0 = new %TYPE(width, height);
}
// @snippet size_char_ct
+
+// @snippet nonConversionRuleForArgumentWithDefaultValue
+ObjectType *tmpObject = nullptr;
+%BEGIN_ALLOW_THREADS
+%RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(&tmpObject);
+%END_ALLOW_THREADS
+%PYARG_0 = PyTuple_New(2);
+PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](%0));
+PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[ObjectType*](tmpObject));
+// @snippet nonConversionRuleForArgumentWithDefaultValue
+
+// @snippet reparent-layout-items
+static void reparent_layout_items(PyObject *parent, PyObject *layout)
+{
+ // CHECKTYPE and ISCONVERTIBLE are used here for test purposes, don't change them.
+ if (!%CHECKTYPE[ObjectTypeLayout*](layout) && !%ISCONVERTIBLE[ObjectTypeLayout*](layout))
+ return;
+ /* %CHECKTYPE[ObjectTypeLayout*](layout) */
+ /* %ISCONVERTIBLE[ObjectTypeLayout*](layout) */
+ ObjectTypeLayout *var;
+ var = %CONVERTTOCPP[ObjectTypeLayout*](layout);
+ // TODO-CONVERTER: erase this
+ // ObjectTypeLayout* var2 = %CONVERTTOCPP[ObjectTypeLayout*](layout);
+ const ObjectTypeList &objChildren = var->objects();
+ for (auto *child : objChildren) {
+ if (child->isLayoutType()) {
+ auto *childLayout = reinterpret_cast<ObjectTypeLayout*>(child);
+ reparent_layout_items(parent, %CONVERTTOPYTHON[ObjectTypeLayout*](childLayout));
+ Shiboken::Object::setParent(layout, %CONVERTTOPYTHON[ObjectTypeLayout*](childLayout));
+ } else {
+ Shiboken::Object::setParent(parent, %CONVERTTOPYTHON[ObjectType*](child));
+ }
+ }
+}
+// @snippet reparent-layout-items
+
+// @snippet fix-margins-parameters
+int a0, a1, a2, a3;
+%BEGIN_ALLOW_THREADS
+%CPPSELF->::%TYPE::%FUNCTION_NAME(&a0, &a1, &a2, &a3);
+%END_ALLOW_THREADS
+%PYARG_0 = PyTuple_New(4);
+PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[int](a0));
+PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[int](a1));
+PyTuple_SetItem(%PYARG_0, 2, %CONVERTTOPYTHON[int](a2));
+PyTuple_SetItem(%PYARG_0, 3, %CONVERTTOPYTHON[int](a3));
+// @snippet fix-margins-parameters
+
+// @snippet fix-margins-return
+PyObject *obj = %PYARG_0.object();
+bool ok = false;
+if (PySequence_Check(obj) != 0 && PySequence_Size(obj) == 4) {
+ Shiboken::AutoDecRef m0(PySequence_GetItem(obj, 0));
+ Shiboken::AutoDecRef m1(PySequence_GetItem(obj, 1));
+ Shiboken::AutoDecRef m2(PySequence_GetItem(obj, 2));
+ Shiboken::AutoDecRef m3(PySequence_GetItem(obj, 3));
+ ok = PyNumber_Check(m0) != 0 && PyNumber_Check(m1) != 0
+ && PyNumber_Check(m2) && PyNumber_Check(m3) != 0;
+ if (ok) {
+ *%1 = %CONVERTTOCPP[int](m0);
+ *%2 = %CONVERTTOCPP[int](m1);
+ *%3 = %CONVERTTOCPP[int](m2);
+ *%4 = %CONVERTTOCPP[int](m3);
+ }
+}
+if (!ok) {
+ PyErr_SetString(PyExc_TypeError, "Sequence of 4 numbers expected");
+ %1 = %2 = %3 = %4 = 0;
+}
+// @snippet fix-margins-return
+
+// @snippet sumArrayAndLength
+bool ok = false;
+if (PySequence_Check(%PYARG_1) != 0) {
+ if (int *array = Shiboken::sequenceToIntArray(%PYARG_1, true)) {
+ ok = PyErr_Occurred() == nullptr;
+ if (ok) {
+ %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(array);
+ %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
+ }
+ delete [] array;
+ }
+}
+if (!ok)
+ PyErr_SetString(PyExc_TypeError, "Should be a sequence of ints");
+// @snippet sumArrayAndLength
+
+// @snippet callArrayMethod
+const Py_ssize_t numItems = PySequence_Size(%PYARG_1);
+Shiboken::ArrayPointer<int> cppItems(numItems);
+for (Py_ssize_t i = 0; i < numItems; i++) {
+ Shiboken::AutoDecRef _obj(PySequence_GetItem(%PYARG_1, i));
+ cppItems[i] = %CONVERTTOCPP[int](_obj);
+}
+%RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(numItems, cppItems);
+%PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
+// @snippet callArrayMethod
+
+// @snippet applyHomogeneousTransform
+bool ok{};
+%RETURN_TYPE retval = %FUNCTION_NAME(%1, %2, %3, %4, %5, %6, %7, %8, %9, %10, &ok);
+if (ok)
+ %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](retval);
+else
+ %PYARG_0 = Py_None;
+// @snippet applyHomogeneousTransform
+
+// @snippet test-argc-argv
+int argc;
+char **argv;
+if (!Shiboken::listToArgcArgv(%PYARG_1, &argc, &argv)) {
+ PyErr_SetString(PyExc_TypeError, "error");
+ return 0;
+}
+%RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(argc, argv);
+%PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
+Shiboken::deleteArgv(argc, argv);
+// @snippet test-argc-argv
+
+// @snippet sum2d
+using Inner = std::list<int>;
+
+int result = 0;
+for (const Inner &inner : %1)
+ result += std::accumulate(inner.cbegin(), inner.cend(), 0);
+
+%PYARG_0 = %CONVERTTOPYTHON[int](result);
+// @snippet sum2d
+
+// @snippet sumproduct
+using Pair = std::pair<int, int>;
+
+int result = 0;
+for (const Pair &p : %1)
+ result += p.first * p.second;
+%PYARG_0 = %CONVERTTOPYTHON[int](result);
+// @snippet sumproduct
+
+// @snippet time-comparison
+static bool compareTime(const Time &t, PyObject *rhs, bool defaultValue)
+{
+ if (!PyDateTimeAPI)
+ PyDateTime_IMPORT;
+ if (PyTime_Check(rhs) == 0)
+ return defaultValue;
+ const int pyH = PyDateTime_TIME_GET_HOUR(rhs);
+ const int pyM = PyDateTime_TIME_GET_MINUTE(rhs);
+ const int pyS = PyDateTime_TIME_GET_SECOND(rhs);
+ return pyH == t.hour() && pyM == t.minute() && pyS == t.second();
+}
+// @snippet time-comparison
+
+// @snippet point-str
+const int x1 = int(%CPPSELF.x());
+const int x2 = int((%CPPSELF.x() * 100) - (x1 * 100));
+const int y1 = int(%CPPSELF.y());
+const int y2 = int((%CPPSELF.y() * 100) - (y1 * 100));
+%PYARG_0 = Shiboken::String::fromFormat("%TYPE(%d.%d, %d.%d)", x1, x2, y1, y2);
+// @snippet point-str
+
+// @snippet point-repr
+const int x1 = int(%CPPSELF.x());
+const int x2 = int((%CPPSELF.x() * 10) - (x1 * 10));
+const int y1 = int(%CPPSELF.y());
+const int y2 = int((%CPPSELF.y() * 10) - (y1 * 10));
+%PYARG_0 = Shiboken::String::fromFormat("<%TYPE object at %p: (%d.%d, %d.%d)>",
+ %CPPSELF, x1, x2, y1, y2);
+// @snippet point-repr
+
+// @snippet point-reduce
+PyObject *type = PyObject_Type(%PYSELF);
+PyObject *args = Py_BuildValue("(dd)", %CPPSELF.x(), %CPPSELF.y());
+%PYARG_0 = Py_BuildValue("(OO)", type, args);
+// @snippet point-reduce
<?xml version="1.0" encoding="UTF-8"?>
<typesystem package="sample">
+
+ <inject-code class="native" position="beginning"
+ file="samplesnippets.cpp" snippet="module-helpers"/>
+
<primitive-type name="ObjectType::Identifier"/>
<primitive-type name="std::nullptr_t"/>
</conversion-rule>
</primitive-type>
- <inject-code class="native" position="beginning">
- static bool Check2TupleOfNumbers(PyObject* pyIn) {
- if (!PySequence_Check(pyIn) || !(PySequence_Size(pyIn) == 2))
- return false;
- Shiboken::AutoDecRef pyReal(PySequence_GetItem(pyIn, 0));
- if (!PyNumber_Check(pyReal))
- return false;
- Shiboken::AutoDecRef pyImag(PySequence_GetItem(pyIn, 1));
- if (!PyNumber_Check(pyImag))
- return false;
- return true;
- }
- </inject-code>
<primitive-type name="Complex" target-lang-api-name="PyComplex">
<include file-name="complex.h" location="global"/>
<conversion-rule>
</conversion-rule>
</primitive-type>
- <inject-code class="native" position="beginning">
- static bool checkPyCapsuleOrPyCObject(PyObject* pyObj)
- {
- return PyCapsule_CheckExact(pyObj);
- }
- </inject-code>
-
<primitive-type name="PrimitiveStructPtr">
<include file-name="handle.h" location="local"/>
<conversion-rule>
</conversion-rule>
</primitive-type>
+ <value-type name="MoveOnly"/>
+ <object-type name="MoveOnlyHandler"/>
+
<!-- As of Qt 6, there is a trend of hiding bool returns of comparison
operators of container classes behind some template expression using
SFINAE depending on their value's traits, like:
Py_ssize_t idx = 0;
for (const auto &s : %in) {
PStr cppItem(s);
- PyList_SET_ITEM(%out, idx++, %CONVERTTOPYTHON[PStr](cppItem));
+ PyList_SetItem(%out, idx++, %CONVERTTOPYTHON[PStr](cppItem));
}
return %out;
</native-to-target>
</add-function>
<add-function signature="getConversionTypeString(const char*)" return-type="PyObject">
- <inject-code class="target" position="beginning">
- Shiboken::Conversions::SpecificConverter converter(%1);
- const char* %0 = 0;
- switch (converter.conversionType()) {
- case Shiboken::Conversions::SpecificConverter::CopyConversion:
- %0 = "Copy conversion";
- break;
- case Shiboken::Conversions::SpecificConverter::PointerConversion:
- %0 = "Pointer conversion";
- break;
- case Shiboken::Conversions::SpecificConverter::ReferenceConversion:
- %0 = "Reference conversion";
- break;
- default:
- %0 = "Invalid conversion";
- }
- %PYARG_0 = %CONVERTTOPYTHON[const char*](%0);
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="samplesnippets.cpp" snippet="getConversionTypeString"/>
</add-function>
- <inject-code class="native" position="beginning">
- static PyObject* __convertCppValuesToPython(const char** typeName, void** values, int size)
- {
- PyObject* result = PyTuple_New(size);
- for (int i = 0; i < size; ++i) {
- Shiboken::Conversions::SpecificConverter converter(typeName[i]);
- PyTuple_SetItem(result, i, converter.toPython(values[i]));
- }
- return result;
- }
- </inject-code>
<add-function signature="convertValueTypeToCppAndThenToPython(Point,Point*,Point&)" return-type="PyObject">
- <inject-code class="target" position="beginning">
- const char* typeNames[] = { "Point", "Point*", "Point&" };
- void* values[] = { &%1, &%2, &(%3) };
- %PYARG_0 = __convertCppValuesToPython(typeNames, values, 3);
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="samplesnippets.cpp" snippet="convertValueTypeToCppAndThenToPython"/>
</add-function>
<add-function signature="convertObjectTypeToCppAndThenToPython(ObjectType*,ObjectType&)" return-type="PyObject">
- <inject-code class="target" position="beginning">
- const char* typeNames[] = { "ObjectType*", "ObjectType&" };
- void* values[] = { &%1, &(%2) };
- %PYARG_0 = __convertCppValuesToPython(typeNames, values, 2);
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="samplesnippets.cpp" snippet="convertObjectTypeToCppAndThenToPython"/>
</add-function>
<add-function signature="convertListOfIntegersToCppAndThenToPython(std::list<int>)" return-type="PyObject">
- <inject-code class="target" position="beginning">
- const char* typeNames[] = { "std::list<int>" };
- void* values[] = { &%1 };
- %PYARG_0 = __convertCppValuesToPython(typeNames, values, 1);
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="samplesnippets.cpp" snippet="convertListOfIntegersToCppAndThenToPython"/>
</add-function>
<add-function signature="convertIntegersToCppAndThenToPython(int,int)" return-type="PyObject">
- <inject-code class="target" position="beginning">
- const char* typeNames[] = { "int", "int" };
- void* values[] = { &%1, &%2 };
- %PYARG_0 = __convertCppValuesToPython(typeNames, values, 2);
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="samplesnippets.cpp" snippet="convertIntegersToCppAndThenToPython"/>
</add-function>
<template name="cpp_indexed_list_to_pylist_conversion">
Py_ssize_t idx = 0;
for (auto it = %in.cbegin(), end = %in.cend(); it != end; ++it, ++idx) {
%INTYPE_0 cppItem(*it);
- PyList_SET_ITEM(%out, idx, %CONVERTTOPYTHON[%INTYPE_0](cppItem));
+ PyList_SetItem(%out, idx, %CONVERTTOPYTHON[%INTYPE_0](cppItem));
}
return %out;
</template>
</target-to-native>
</conversion-rule>
</container-type>
- <add-function signature="cacheSize()" return-type="int">
+ <add-function signature="wrapperCount()" return-type="int">
<inject-code class="target">
%RETURN_TYPE %0 = Shiboken::BindingManager::instance().getAllPyObjects().size();
%PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
<object-type name="Abstract">
<enum-type name="Type"/>
<enum-type name="PrintFormat"/>
- <modify-function signature="id()" rename="id_"/>
+ <modify-function signature="id()const" rename="id_"/>
<modify-function signature="hideFunction(HideType*)" remove="all"/>
<modify-field name="toBeRenamedField" rename="renamedField"/>
<modify-field name="readOnlyField" write="false"/>
<parent index="1" action="add"/>
</modify-argument>
</modify-function>
- <inject-code class="native" position="beginning">
- static void reparent_layout_items(PyObject* parent, PyObject* layout)
- {
- // CHECKTYPE and ISCONVERTIBLE are used here for test purposes, don't change them.
- if (!%CHECKTYPE[ObjectTypeLayout*](layout) && !%ISCONVERTIBLE[ObjectTypeLayout*](layout))
- return;
- /* %CHECKTYPE[ObjectTypeLayout*](layout) */
- /* %ISCONVERTIBLE[ObjectTypeLayout*](layout) */
- ObjectTypeLayout* var;
- var = %CONVERTTOCPP[ObjectTypeLayout*](layout);
- // TODO-CONVERTER: erase this
- /*
- ObjectTypeLayout* var2 = %CONVERTTOCPP[ObjectTypeLayout*](layout);
- */
- const ObjectTypeList& objChildren = var->objects();
- ObjectTypeList::const_iterator it = objChildren.begin();
- for (; it != objChildren.end(); ++it) {
- if ((*it)->isLayoutType()) {
- ObjectTypeLayout* l = reinterpret_cast<ObjectTypeLayout*>(*it);
- reparent_layout_items(parent, %CONVERTTOPYTHON[ObjectTypeLayout*](l));
- Shiboken::Object::setParent(layout, %CONVERTTOPYTHON[ObjectTypeLayout*](l));
- } else {
- Shiboken::Object::setParent(parent, %CONVERTTOPYTHON[ObjectType*](*it));
- }
- }
- }
- </inject-code>
+ <inject-code class="native" position="beginning"
+ file="samplesnippets.cpp" snippet="reparent-layout-items"/>
<modify-function signature="setLayout(ObjectTypeLayout*)">
<modify-argument index="1">
<parent index="this" action="add"/>
<modify-argument index="2">
<replace-type modified-type="PySequence" />
<conversion-rule class="native">
- Shiboken::AutoArrayPointer<Point> %out(%1);
+ Shiboken::ArrayPointer<Point> %out(%1);
for (Py_ssize_t i = 0; i < %1; ++i) {
Shiboken::AutoDecRef _obj(PySequence_GetItem(%PYARG_1, i));
%out[i] = %CONVERTTOCPP[Point](_obj);
<modify-argument index="return">
<replace-type modified-type="(status, object)"/>
</modify-argument>
- <inject-code class="target" position="beginning">
- ObjectType* tmpObject = 0;
- %BEGIN_ALLOW_THREADS
- %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(&tmpObject);
- %END_ALLOW_THREADS
- %PYARG_0 = PyTuple_New(2);
- PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[%RETURN_TYPE](%0));
- PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[ObjectType*](tmpObject));
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="samplesnippets.cpp" snippet="nonConversionRuleForArgumentWithDefaultValue"/>
</modify-function>
<modify-function signature="passOddBool(OddBool)" rename="invertBoolean">
<inject-code class="target" position="beginning">
</modify-function>
<modify-function signature="callMe()">
<inject-code class="native" position="end">
- PyObject_Call(%PYTHON_METHOD_OVERRIDE, %PYTHON_ARGUMENTS, NULL);
+ PyObject_Call(%PYTHON_METHOD_OVERRIDE, %PYTHON_ARGUMENTS, nullptr);
</inject-code>
</modify-function>
<modify-function signature="createStr(const char*, Str*&)">
</conversion-rule>
</modify-argument>
<inject-code class="target" position="beginning">
- Str* _str_arg_ = 0;
- %RETURN_TYPE %0 = %CPPSELF.%TYPE::%FUNCTION_NAME(%1, _str_arg_);
+ Str *strArg = nullptr;
+ %RETURN_TYPE %0 = %CPPSELF.%TYPE::%FUNCTION_NAME(%1, strArg);
</inject-code>
<inject-code class="target" position="end">
%PYARG_0 = PyTuple_New(2);
PyObject* _item_ = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
PyTuple_SetItem(%PYARG_0, 0, _item_);
- _item_ = %CONVERTTOPYTHON[Str*](_str_arg_);
+ _item_ = %CONVERTTOPYTHON[Str*](strArg);
PyTuple_SetItem(%PYARG_0, 1, _item_);
</inject-code>
</modify-function>
PyTuple_SetItem(%PYARG_0, 1, _item_);
</inject-code>
</modify-function>
- <template name="fix_int*,int*,int*,int*">
- int a0, a1, a2, a3;
- %BEGIN_ALLOW_THREADS
- %CPPSELF->::%TYPE::%FUNCTION_NAME(&a0, &a1, &a2, &a3);
- %END_ALLOW_THREADS
- %PYARG_0 = PyTuple_New(4);
- PyTuple_SetItem(%PYARG_0, 0, %CONVERTTOPYTHON[int](a0));
- PyTuple_SetItem(%PYARG_0, 1, %CONVERTTOPYTHON[int](a1));
- PyTuple_SetItem(%PYARG_0, 2, %CONVERTTOPYTHON[int](a2));
- PyTuple_SetItem(%PYARG_0, 3, %CONVERTTOPYTHON[int](a3));
- </template>
- <template name="fix_native_return_int*,int*,int*,int*">
- PyObject* _obj = %PYARG_0;
- Shiboken::AutoDecRef _obj0(PySequence_GetItem(_obj, 0));
- Shiboken::AutoDecRef _obj1(PySequence_GetItem(_obj, 1));
- Shiboken::AutoDecRef _obj2(PySequence_GetItem(_obj, 2));
- Shiboken::AutoDecRef _obj3(PySequence_GetItem(_obj, 3));
- if (!PySequence_Check(_obj)
- || PySequence_Size(_obj) != 4
- || !PyNumber_Check(_obj0)
- || !PyNumber_Check(_obj1)
- || !PyNumber_Check(_obj2)
- || !PyNumber_Check(_obj3)) {
- PyErr_SetString(PyExc_TypeError, "Sequence of 4 numbers expected");
- } else {
- *%1 = %CONVERTTOCPP[int](_obj0);
- *%2 = %CONVERTTOCPP[int](_obj1);
- *%3 = %CONVERTTOCPP[int](_obj2);
- *%4 = %CONVERTTOCPP[int](_obj3);
- }
- </template>
<modify-function signature="getMargins(int*,int*,int*,int*)const">
<modify-argument index="return" pyi-type="Tuple[int, int, int, int]">
<replace-type modified-type="PyObject" />
<remove-argument/>
<remove-default-expression/>
</modify-argument>
- <inject-code class="target" position="beginning">
- <insert-template name="fix_int*,int*,int*,int*"/>
- </inject-code>
- <inject-code class="native" position="end">
- <insert-template name="fix_native_return_int*,int*,int*,int*"/>
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="samplesnippets.cpp" snippet="fix-margins-parameters"/>
+ <inject-code class="native" position="end"
+ file="samplesnippets.cpp" snippet="fix-margins-return"/>
</modify-function>
<modify-function signature="callGetMargins(int*,int*,int*,int*)const">
<modify-argument index="0">
<remove-argument/>
<remove-default-expression/>
</modify-argument>
- <inject-code class="target" position="beginning">
- <insert-template name="fix_int*,int*,int*,int*"/>
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="samplesnippets.cpp" snippet="fix-margins-parameters"/>
</modify-function>
<modify-function signature="recursionOnModifiedVirtual(Str)const">
<inject-code class="target" position="beginning">
<modify-argument index="11">
<remove-argument/>
</modify-argument>
- <inject-code class="target" position="beginning">
- bool ok_;
- %RETURN_TYPE retval_ =
- %FUNCTION_NAME(%1, %2, %3, %4, %5, %6, %7, %8, %9, %10, &ok_);
- if (!ok_)
- %PYARG_0 = Py_None;
- else
- %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](retval_);
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="samplesnippets.cpp" snippet="applyHomogeneousTransform"/>
</modify-function>
</function>
<!-- Tests add-function for nested template types -->
<add-function signature="sum2d(std::list<std::list<int> >)" return-type="int">
- <inject-code class="target" position="beginning">
- typedef std::list<int> Inner;
- typedef std::list<Inner> Outer;
-
- int result = 0;
-
- Outer::const_iterator oiter, oend = %1.end();
- for (oiter = %1.begin(); oiter != oend; ++oiter) {
- const Inner& inner = *oiter;
- Inner::const_iterator iiter, iend = inner.end();
- for (iiter = inner.begin(); iiter != iend; ++iiter)
- result += *iiter;
- }
-
- %PYARG_0 = %CONVERTTOPYTHON[int](result);
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="samplesnippets.cpp" snippet="sum2d"/>
</add-function>
<!-- Tests add-function for nested template types -->
<add-function signature="sumproduct(std::list<std::pair<int, int> >)" return-type="int">
- <inject-code class="target" position="beginning">
- typedef std::pair<int, int> Pair;
- typedef std::list<Pair> List;
-
- int result = 0;
-
- List::const_iterator iter, end = %1.end();
- for (iter = %1.begin(); iter != end; ++iter)
- result += iter->first * iter->second;
-
- %PYARG_0 = %CONVERTTOPYTHON[int](result);
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="samplesnippets.cpp" snippet="sumproduct"/>
</add-function>
<modify-argument index="1">
<replace-type modified-type="PyObject"/>
</modify-argument>
- <inject-code class="target" position="beginning">
- int* array = NULL;
- bool errorOccurred = false;
-
- if (PySequence_Check(%PYARG_1)) {
- if((array = Shiboken::sequenceToIntArray(%PYARG_1, true)) == NULL && PyErr_Occurred()) {
- PyErr_SetString(PyExc_TypeError, "Should be a sequence of ints");
- errorOccurred = true;
- }
- } else {
- PyErr_SetString(PyExc_TypeError, "Should be a sequence of ints");
- errorOccurred = true;
- }
-
- if (!errorOccurred) {
- %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(array);
- if (array)
- delete[] array;
- %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
- }
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="samplesnippets.cpp" snippet="sumArrayAndLength"/>
</modify-function>
<modify-function signature="arrayMethod(int, int*) const">
<replace-type modified-type="PySequence"/>
<conversion-rule class="native">
const Py_ssize_t numItems = PySequence_Size(%PYARG_1);
- Shiboken::AutoArrayPointer<int> %out(numItems);
+ Shiboken::ArrayPointer<int> %out(numItems);
for (Py_ssize_t i = 0; i < numItems; ++i) {
Shiboken::AutoDecRef _obj(PySequence_GetItem(%PYARG_1, i));
if (%CHECKTYPE[int](_obj))
<conversion-rule class="target">
PyObject* %out = PyList_New(count);
for (int i = 0; i < count; ++i)
- PyList_SET_ITEM(%out, i, %CONVERTTOPYTHON[int](%in[i]));
+ PyList_SetItem(%out, i, %CONVERTTOPYTHON[int](%in[i]));
</conversion-rule>
</modify-argument>
</modify-function>
<modify-argument index="2">
<replace-type modified-type="PySequence"/>
</modify-argument>
- <inject-code class="target" position="beginning">
- Py_ssize_t numItems = PySequence_Size(%PYARG_1);
- int *cppItems = new int[numItems];
- for (int i = 0; i < numItems; i++) {
- Shiboken::AutoDecRef _obj(PySequence_GetItem(%PYARG_1, i));
- cppItems[i] = %CONVERTTOCPP[int](_obj);
- }
- %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(numItems, cppItems);
- %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
- delete[] cppItems;
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="samplesnippets.cpp" snippet="callArrayMethod"/>
</modify-function>
<!--
<modify-argument index="2">
<remove-argument />
</modify-argument>
- <inject-code class="target" position="beginning">
- int argc;
- char** argv;
- if (!Shiboken::listToArgcArgv(%PYARG_1, &argc, &argv)) {
- PyErr_SetString(PyExc_TypeError, "error");
- return 0;
- }
- %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(argc, argv);
- %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
- for (int i = 0; i < argc; ++i)
- free(argv[i]);
- delete[] argv;
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="samplesnippets.cpp" snippet="test-argc-argv"/>
</modify-function>
<!-- Tested in InjectCodeTest.testArgsModification2 -->
<modify-argument index="2">
<remove-argument />
</modify-argument>
- <inject-code class="target" position="beginning">
- int argc;
- char** argv;
- if (!Shiboken::listToArgcArgv(%PYARG_1, &argc, &argv)) {
- PyErr_SetString(PyExc_TypeError, "error");
- return 0;
- }
- %RETURN_TYPE %0 = %CPPSELF.%FUNCTION_NAME(argc, argv);
- %PYARG_0 = %CONVERTTOPYTHON[%RETURN_TYPE](%0);
- for (int i = 0; i < argc; ++i)
- free(argv[i]);
- delete[] argv;
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="samplesnippets.cpp" snippet="test-argc-argv"/>
</modify-function>
</value-type>
<modify-argument index="1">
<replace-type modified-type="PyObject" />
<conversion-rule class="native">
- unsigned char* %out = 0;
+ unsigned char *%out = nullptr;
</conversion-rule>
</modify-argument>
</modify-function>
<value-type name="Point">
<add-function signature="__str__" return-type="str">
- <inject-code class="target" position="beginning">
- int x1 = (int) %CPPSELF.x();
- int x2 = ((int) (%CPPSELF.x() * 100)) - (x1 * 100);
- int y1 = (int) %CPPSELF.y();
- int y2 = ((int) (%CPPSELF.y() * 100)) - (y1 * 100);
- %PYARG_0 = Shiboken::String::fromFormat("Point(%d.%d, %d.%d)", x1, x2, y1, y2);
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="samplesnippets.cpp" snippet="point-str"/>
</add-function>
<add-function signature="__repr__" return-type="str">
- <inject-code class="target" position="beginning">
- int x1 = (int) %CPPSELF.x();
- int x2 = ((int) (%CPPSELF.x() * 10)) - (x1 * 10);
- int y1 = (int) %CPPSELF.y();
- int y2 = ((int) (%CPPSELF.y() * 10)) - (y1 * 10);
- %PYARG_0 = Shiboken::String::fromFormat("<Point object at %p: (%d.%d, %d.%d)>", %CPPSELF, x1, x2, y1, y2);
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="samplesnippets.cpp" snippet="point-repr"/>
</add-function>
<add-function signature="__reduce__" return-type="str">
- <inject-code class="target" position="beginning">
- PyObject* type = PyObject_Type(%PYSELF);
- PyObject* args = NULL;
-
- args = Py_BuildValue("(dd)", %CPPSELF.x(), %CPPSELF.y());
-
- %PYARG_0 = Py_BuildValue("(OO)", type, args);
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="samplesnippets.cpp" snippet="point-reduce"/>
</add-function>
<modify-function signature="midpoint(const Point&, Point*)const">
<value-type name="PointF">
<add-function signature="__str__" return-type="str">
- <inject-code class="target" position="beginning">
- int x1 = (int) %CPPSELF.x();
- int x2 = ((int) (%CPPSELF.x() * 100)) - (x1 * 100);
- int y1 = (int) %CPPSELF.y();
- int y2 = ((int) (%CPPSELF.y() * 100)) - (y1 * 100);
- %PYARG_0 = Shiboken::String::fromFormat("PointF(%d.%d, %d.%d)", x1, x2, y1, y2);
- </inject-code>
+ file="samplesnippets.cpp" snippet="point-str"/>
</add-function>
<add-function signature="__repr__" return-type="str">
- <inject-code class="target" position="beginning">
- int x1 = (int) %CPPSELF.x();
- int x2 = ((int) (%CPPSELF.x() * 10)) - (x1 * 10);
- int y1 = (int) %CPPSELF.y();
- int y2 = ((int) (%CPPSELF.y() * 10)) - (y1 * 10);
- %PYARG_0 = Shiboken::String::fromFormat("<PointF object at %p: (%d.%d, %d.%d)>", %CPPSELF, x1, x2, y1, y2);
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="samplesnippets.cpp" snippet="point-repr"/>
</add-function>
<add-function signature="__reduce__" return-type="str">
- <inject-code class="target" position="beginning">
- PyObject *type = PyObject_Type(%PYSELF);
- PyObject *args = NULL;
-
- args = Py_BuildValue("(dd)", %CPPSELF.x(), %CPPSELF.y());
-
- %PYARG_0 = Py_BuildValue("(OO)", type, args);
- </inject-code>
+ <inject-code class="target" position="beginning"
+ file="samplesnippets.cpp" snippet="point-reduce"/>
</add-function>
<modify-function signature="midpoint(const PointF&, PointF*)const">
<extra-includes>
<include file-name="datetime.h" location="global"/>
</extra-includes>
+ <inject-code class="native" position="beginning"
+ file="samplesnippets.cpp" snippet="time-comparison"/>
<enum-type name="NumArgs"/>
<add-function signature="operator!=(const PyObject*)" return-type="PyObject">
<inject-code>
- if (!PyDateTimeAPI)
- PyDateTime_IMPORT;
- if (PyTime_Check(%1)) {
- int pyH = PyDateTime_TIME_GET_HOUR(%1);
- int pyM = PyDateTime_TIME_GET_MINUTE(%1);
- int pyS = PyDateTime_TIME_GET_SECOND(%1);
- if ((pyH == %CPPSELF.hour()) &&
- (pyM == %CPPSELF.minute()) &&
- (pyS == %CPPSELF.second()))
- %PYARG_0 = Py_False;
- else
- %PYARG_0 = Py_True;
- Py_INCREF(%PYARG_0);
- }
+ %PYARG_0 = compareTime(%CPPSELF, %1, true) ? Py_False : Py_True;
+ Py_INCREF(%PYARG_0);
</inject-code>
</add-function>
<add-function signature="operator==(const PyObject*)" return-type="PyObject">
<inject-code>
- if (!PyDateTimeAPI)
- PyDateTime_IMPORT;
- if (PyTime_Check(%1)) {
- int pyH = PyDateTime_TIME_GET_HOUR(%1);
- int pyM = PyDateTime_TIME_GET_MINUTE(%1);
- int pyS = PyDateTime_TIME_GET_SECOND(%1);
- if ((pyH == %CPPSELF.hour()) &&
- (pyM == %CPPSELF.minute()) &&
- (pyS == %CPPSELF.second()))
- %PYARG_0 = Py_True;
- else
- %PYARG_0 = Py_False;
- Py_INCREF(%PYARG_0);
- }
+ %PYARG_0 = compareTime(%CPPSELF, %1, false) ? Py_True : Py_False;
+ Py_INCREF(%PYARG_0);
</inject-code>
</add-function>
{
Shiboken::AutoDecRef strList(PySequence_Fast(%PYARG_1, "The argument must be a sequence."));
const Py_ssize_t lineCount = PySequence_Size(strList.object());
- for (int line = 0; line < lineCount; ++line) {
+ for (Py_ssize_t line = 0; line < lineCount; ++line) {
Shiboken::AutoDecRef _obj(PySequence_GetItem(strList.object(), line));
bool isString = Shiboken::String::check(_obj);
if (!isString) {
// PySIDE-1735: Enums are now implemented in Python, so we need to avoid asserts.
if (PyErr_Occurred())
break;
- const char** %out = 0;
+ const char **%out = nullptr;
</conversion-rule>
</modify-argument>
</modify-function>
<modify-argument index="1">
<replace-type modified-type="PyObject" />
<conversion-rule class="native">
- void* %out = 0;
+ void *%out = nullptr;
</conversion-rule>
</modify-argument>
</modify-function>
<replace-type modified-type="PyBytes"/>
</modify-argument>
<inject-code class="target" position="beginning">
- PyObject* data = 0;
+ PyObject *data = nullptr;
if (PyUnicode_CheckExact(%PYARG_1)) {
data = PyUnicode_AsASCIIString(%PYARG_1);
} else {
<typesystem package="smart">
<rejection class="*" argument-type="^std::nullptr_t&?$"/>
- <template name="cpplist_to_pylist_convertion">
- PyObject *%out = PyList_New(int(%in.size()));
- int idx = 0;
- for (const auto &cppItem : %in)
- PyList_SET_ITEM(%out, idx++, %CONVERTTOPYTHON[%INTYPE_0](cppItem));
- return %out;
- </template>
- <template name="pyseq_to_cpplist_convertion">
- Shiboken::AutoDecRef seq(PySequence_Fast(%in, 0));
- for (Py_ssize_t i = 0, size = PySequence_Size(seq.object()); i < size; ++i) {
- Shiboken::AutoDecRef pyItem(PySequence_GetItem(seq.object(), i));
- %OUTTYPE_0 cppItem = %CONVERTTOCPP[%OUTTYPE_0](pyItem);
- %out.push_back(cppItem);
- }
- </template>
-
<!-- Used in tests to check what C++ objects are allocated. -->
<object-type name="Registry" />
#include "dummygenerator.h"
#include "dummygentestconfig.h"
-#include <QtCore/QProcess>
-#include <QtCore/QTemporaryFile>
-#include <QtTest/QTest>
+#include <QtTest/qtest.h>
+
+#include <QtCore/qprocess.h>
+#include <QtCore/qtemporaryfile.h>
#define GENERATED_CONTENTS "// Generated code for class: Dummy"
import os
import sys
from collections import OrderedDict
+from pathlib import Path
from textwrap import dedent
from timeit import default_timer as timer
# PYSIDE-1229: When a fatal error happens, bail out immediately!
if item.fatal:
fatal = item
- print()
+
+ print("\n #### Top 20 slow tests:")
+ for item in results.get_slowest_tests(20):
+ print(f" {item.mod_name:<50} {item.time:6}s")
+
print(
- f"Totals: {sum(r)} tests. "
+ f"\nTotals: {sum(r)} tests. "
f"{r[0]} passed, {r[1]} failed, {r[2]} skipped, {r[3]} blacklisted, {r[4]} bpassed."
)
print()
group.add_argument(
"--blacklist",
"-b",
- type=argparse.FileType("r"),
+ type=str,
default=blacklist_default,
help=f"a Qt blacklist file (default: {blacklist_default})",
)
)
parser_getcwd = subparsers.add_parser("getcwd")
parser_getcwd.add_argument(
- "filename", type=argparse.FileType("w"), help="write the build dir name into a file"
+ "filename", type=str, help="write the build dir name into a file"
)
parser_getcwd.add_argument(
"--buildno",
sys.exit(1)
if args.subparser_name == "getcwd":
- print(builds.selected.build_dir, file=args.filename)
- print(builds.selected.build_dir, "written to file", args.filename.name)
+ Path(args.filename).write_text(builds.selected.build_dir + '\n')
+ print(builds.selected.build_dir, "written to file", args.filename)
sys.exit(0)
elif args.subparser_name == "test":
runs = args.reruns
sys.exit(1)
if args.blacklist:
- args.blacklist.close()
- bl = BlackList(args.blacklist.name)
+ bl = BlackList(args.blacklist)
else:
bl = BlackList(None)
if args.environ:
import os
import re
-from collections import namedtuple
+from dataclasses import dataclass
from io import StringIO
"""
assert len(re.match(_TEST_PAT, _EXAMPLE.splitlines()[5], re.VERBOSE).groups()) == 8
assert len(re.match(_TEST_PAT, _EXAMPLE.splitlines()[7], re.VERBOSE).groups()) == 8
-TestResult = namedtuple(
- "TestResult", "idx n sharp mod_name passed " "code time fatal rich_result".split()
-)
+
+@dataclass
+class TestResult:
+ idx: int = 0
+ n: int = 0
+ sharp: int = 0
+ mod_name: str = ""
+ passed: bool = False
+ code: str = ""
+ time: float = 0
+ fatal: bool = False
+ rich_result: str = ""
+
+
+def sort_time_key(item):
+ return item.time
def _parse_tests(test_log):
if idx + 1 != item.idx:
# The numbering is disrupted. Provoke an error in this line!
code = f"{code}, but lines are disrupted!"
- result[idx] = item._replace(
- passed=False, code=f"{item.code}, but lines are disrupted!", fatal=True
- )
+ result[idx].passed = False
+ result[idx].code = f"{item.code}, but lines are disrupted!"
+ result[idx].fatal = True
break
return result
if item.fatal:
# PYSIDE-1229: Stop the testing completely when a fatal error exists
res = "FATAL"
- yield item._replace(rich_result=res)
+ item.rich_result = res
+ yield item
+
+ def get_slowest_tests(self, max_count):
+ result = self.results.copy()
+ result.sort(key=sort_time_key, reverse=True)
+ if len(result) > max_count:
+ result = result[0:max_count - 1]
+ return result
build_scripts_dir = os.path.abspath(os.path.join(this_dir, ".."))
sys.path.append(build_scripts_dir)
-from build_scripts.utils import detect_clang
+from build_scripts.utils import detect_clang # noqa: E402
class TestRunner:
Helper for _find_ctest() that finds the ctest binary in a build
system file (ninja, Makefile).
"""
- look_for = "--force-new-ctest-process"
+ # Looking for a command ending this way:
+ look_for = "\\ctest.exe" if "win32" in sys.platform else "/ctest"
line = None
with open(file_name) as makefile:
for line in makefile:
- if look_for in line:
+ if look_for in line and line.lstrip().startswith("COMMAND"):
break
else:
# We have probably forgotten to build the tests.
raise RuntimeError(msg)
# the ctest program is on the left to look_for
assert line, f"Did not find {look_for}"
- ctest = re.search(r'(\S+|"([^"]+)")\s+' + look_for, line).groups()
+ look = re.escape(look_for)
+ ctest = re.search(fr'(\S+{look}|"([^"]+{look})")', line).groups()
return ctest[1] or ctest[0]
def _find_ctest(self):
# Note: shiboken6_generator is not needed for compile_using_nuitka,
# but building modules with cmake needs it.
if NEW_WHEELS:
- return ["shiboken6", "shiboken6_generator", "PySide6_Essentials", "PySide6_Addons",
- "PySide6"]
+ return ["shiboken6", "shiboken6_generator", "pyside6_essentials", "pyside6_addons",
+ "pyside6"]
else:
- return ["shiboken6", "shiboken6_generator", "PySide6"]
+ return ["shiboken6", "shiboken6_generator", "pyside6"]
def clean_egg_info():
# the tag number does not matter much since we update the sdk later
DEFAULT_SDK_TAG = 6514223
-ANDROID_NDK_VERSION = "26b"
-ANDROID_NDK_VERSION_NUMBER_SUFFIX = "10909125"
+ANDROID_NDK_VERSION = "27c"
+ANDROID_NDK_VERSION_NUMBER_SUFFIX = "12479018"
def run_command(command: list[str], cwd: str | None = None, ignore_fail: bool = False,
print("Unpacking Android Ndk")
if sys.platform == "darwin":
- extract_dmg(file=(ndk_path
- / f"android-ndk-r{ANDROID_NDK_VERSION}-{sys.platform}.{ndk_extension}"),
- destination=ndk_path)
- ndk_version_path = (ndk_version_path
- / (f"AndroidNDK{ANDROID_NDK_VERSION_NUMBER_SUFFIX}.app"
- "/Contents/NDK"))
+ extract_dmg(file=ndk_zip_path, destination=ndk_path)
else:
- extract_zip(file=(ndk_path
- / f"android-ndk-r{ANDROID_NDK_VERSION}-{sys.platform}.{ndk_extension}"),
- destination=ndk_path)
+ extract_zip(file=ndk_zip_path, destination=ndk_path)
except Exception as e:
print(f"Error occurred while downloading and unpacking Android NDK: {e}")
if ndk_path.exists():
parser.add_argument("-v", "--verbose", help="run in verbose mode", action="store_const",
dest="loglevel", const=logging.INFO)
- parser.add_argument("--api-level", type=str, default="34",
+ parser.add_argument("--api-level", type=str, default="35",
help="Minimum Android API level to use")
parser.add_argument("--ndk-path", type=str, help="Path to Android NDK (Preferred r26b)")
# sdk path is needed to compile all the Qt Java Acitivity files into Qt6AndroidBindings.jar
-fomit-frame-pointer \
-march={{ gcc_march }} \
-msse4.2 \
- -mpopcnt \
-m{{ plat_bits }} \
-fPIC \
-I{{ target_python_path }}/include/python{{ python_version }} \
-Wno-unused-command-line-argument")
+
+if (NOT CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")
+ set(QT_COMPILER_FLAGS "${QT_COMPILER_FLAGS} -mpopcnt")
+endif()
+
set(QT_COMPILER_FLAGS_RELEASE "-O2 -pipe")
# FIXME
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
from __future__ import annotations
-import os
import subprocess
import sys
from argparse import ArgumentParser, RawTextHelpFormatter
module_dependency_dict = {}
for m in SOURCE_DIR.glob("Qt*"):
module = m.name
- # QtGraphs duplicates symbols from QtDataVisualization causing shiboken errors
- if module == "QtDataVisualization":
+ # QtGraphs duplicates symbols from QtDataVisualization/QtCharts causing shiboken errors
+ if module == "QtDataVisualization" or module == "QtCharts":
continue
qt_include_path = qt_include_dir / module
if qt_include_path.is_dir():
print(f"example_gallery: No .rst or .md file found in {original_dir}")
continue
- with original_file.open("r") as f:
+ with original_file.open("r", encoding="utf-8") as f:
# Read the first line
first_line = f.readline().strip()
handle_type_var_declaration, handle_useless_qt_classes,
handle_new,
handle_void_functions, handle_qt_connects)
+from enum_migration import qualify_enums
from parse_utils import dstrip, get_indent, remove_ref
if qt_connects:
return qt_connects
+ x = qualify_enums(x)
+
# Handle "->"
if "->" in x:
x = x.replace("->", ".")
--- /dev/null
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+MOUSE_BUTTONS = ("NoButton", "AllButtons", "LeftButton", "RightButton", "MiddleButton",
+ "BackButton", "XButton", "ExtraButton", "ForwardButton", "ExtraButton",
+ "TaskButton")
+
+MODIFIERS = ("NoModifier", "ShiftModifier", "ControlModifier", "AltModifier",
+ "MetaModifier", "KeypadModifier", "GroupSwitchModifier")
+
+GLOBAL_COLORS = ("white", "black", "red", "darkRed", "green", "darkGreen", "blue",
+ "darkBlue", "cyan", "darkCyan", "magenta", "darkMagenta", "yellow",
+ "darkYellow", "gray", "darkGray", "lightGray", "transparent")
+
+ALIGN_VALUES = ("AlignHCenter", "AlignLeft", "AlignCenter", "AlignRight", "AlignVCenter",
+ "AlignTop", "AlignBotton", "AlignJustify", "AlignBaseline", "AlignAbsolute",
+ "AlignLeading", "Trailing")
+
+
+def _get_replacements():
+ result = [
+ ("Qt::Key_", "Qt.Key.Key_"),
+ ("Qt::CTRL", "Qt.Modifier.CTRL"),
+ ("Qt::ALT", "Qt.Modifier.ALT"),
+ ("Qt::CaseInsensitive", "Qt.CaseSensitivity.CaseInsensitive"),
+ ("Qt::CaseSensitive", "Qt.CaseSensitivity.CaseSensitive"),
+ ("QImage::Format_", "QImage.Format.Format_"),
+ ("Qt::WA_DeleteOnClose", "Qt.WidgetAttribute.WA_DeleteOnClose"),
+ ("QQuickView::Ready", "QQuickView.Status.Ready"),
+ ("QQuickView::Error", "QQuickView.Status.Error"),
+ ("QQuickView::Loading", "QQuickView.Status.Loading"),
+ ("QPainter::Antialiasing", "QPainter.RenderHint.Antialiasing"),
+ ("QQuickView::SizeRootObjectToView", "QQuickView.ResizeMode.SizeRootObjectToView"),
+ ("QQuickView::SizeViewToRootObject", "QQuickView.ResizeMode.SizeViewToRootObject"),
+ ("QKeySequence::", "QKeySequence.StandardKey."),
+ ("QEvent::", "QEvent.Type.")
+ ]
+ for c in GLOBAL_COLORS:
+ result.append((f"Qt::{c}", f"Qt.GlobalColor.{c}"))
+ for b in ("Close", "Ok", "Cancel", "Yes", "No"):
+ result.append((f"QDialogButtonBox::{b}", f"QDialogButtonBox.StandardButton.{b}"))
+ result.append((f"QMessageBox::{b}", f"QMessageBox.StandardButton.{b}"))
+ for b in MOUSE_BUTTONS:
+ result.append((f"Qt::{b}", f"Qt.MouseButton.{b}"))
+ for a in ALIGN_VALUES:
+ result.append((f"Qt::{a}", f"Qt.AlignmentFlag.{a}"))
+ for m in MODIFIERS:
+ result.append((f"Qt::{m}", f"Qt.KeyboardModifier.{m}"))
+ for m in ("ReadOnly", "WriteOnly", "Text"):
+ result.append((f"QIODevice::{m}", f"QIODevice.OpenModeFlag.{m}"))
+ result.append((f"QFile::{m}", f"QFile.OpenModeFlag.{m}"))
+ for p in ("Preferred", "Ignored", "Fixed", "Maximum", "Minimum", "Expanding"):
+ result.append((f"QSizePolicy::{p}", f"QSizePolicy.Policy.{p}"))
+ for r in ("DisplayRole", "EditRole"):
+ result.append((f"Qt::{r}", f"Qt.ItemDataRole.{r}"))
+ for f in ("Box", "StyledPanel", "Panel", "WinPanel", "NoFrame"):
+ result.append((f"QFrame::{f}", "QFrame.Shape.{f}"))
+ for f in ("Raised", "Sunken"):
+ result.append((f"QFrame::{f}", "QFrame.Shadow.{f}"))
+ return result
+
+
+REPLACEMENTS = _get_replacements()
+
+
+def qualify_enums(s):
+ for replacement in REPLACEMENTS:
+ s = s.replace(replacement[0], replacement[1])
+ return s
{
- "files": ["main.py", "converter.py", "handlers.py", "override.py",
+ "files": ["main.py", "converter.py", "handlers.py", "override.py", "enum_migration.py",
"tests/test_converter.py", "tests/test_snippets.py"]
}
assert st("else {") == "else:"
+def test_qualify_enumerations():
+ assert st("Qt::Key_A") == "Qt.Key.Key_A"
+
+
def test_new():
assert st("a = new Something(...);") == "a = Something(...)"
assert st("a = new Something") == "a = Something()"
dynamic = ["version"]
requires-python = ">=3.9, <3.14"
keywords = ["Qt"]
-license = {text = "LGPL"}
+license = {text="LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only"}
dependencies = "PROJECT_DEPENDENCIES"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: X11 Applications :: Qt",
"Environment :: Win32 (MS Windows)",
"Intended Audience :: Developers",
- "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
- "License :: Other/Proprietary License",
"Operating System :: MacOS :: MacOS X",
"Operating System :: POSIX",
"Operating System :: POSIX :: Linux",